From c264af79bf31fbbd31efe90d746a7d76f4bd572d Mon Sep 17 00:00:00 2001 From: Elisa Jasinska Date: Mon, 19 Sep 2016 00:29:03 +0200 Subject: [PATCH 01/25] adding coveralls --- .travis.yml | 9 ++++++--- README.md | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2e89493..bd37bc7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ install: - pip install -r requirements.txt - pip install . - pip install -e git+https://github.com/napalm-automation/napalm-base.git@develop#egg=napalm-base + - pip install coveralls deploy: provider: pypi @@ -19,7 +20,9 @@ deploy: script: - cd test/unit -- nosetests -v TestNXOSDriver:TestGetterNXOSDriver -- nosetests -v TestNXOSDriver:TestNXOSDriver_bgp_neighbors -- nosetests -v TestNXOSDriver:TestNXOSDriver_get_interfaces_ip +- nosetests --with-coverage --cover-package napalm_nxos -v TestNXOSDriver:TestGetterNXOSDriver +- nosetests --with-coverage --cover-package napalm_nxos -v TestNXOSDriver:TestNXOSDriver_bgp_neighbors +- nosetests --with-coverage --cover-package napalm_nxos -v TestNXOSDriver:TestNXOSDriver_get_interfaces_ip - cd ../.. +- coverage combine test/unit/.coverage +after_success: coveralls diff --git a/README.md b/README.md index 4709f84..0df7e34 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![PyPI](https://img.shields.io/pypi/v/napalm-nxos.svg)](https://pypi.python.org/pypi/napalm-nxos) [![PyPI](https://img.shields.io/pypi/dm/napalm-nxos.svg)](https://pypi.python.org/pypi/napalm-nxos) [![Build Status](https://travis-ci.org/napalm-automation/napalm-nxos.svg?branch=master)](https://travis-ci.org/napalm-automation/napalm-nxos) +[![Coverage Status](https://coveralls.io/repos/github/napalm-automation/napalm-nxos/badge.svg?branch=master)](https://coveralls.io/github/napalm-automation/napalm-nxos) # napalm-nxos From 5e823b02d669d4158827f155f723c38953cf9a20 Mon Sep 17 00:00:00 2001 From: Lubo Date: Sun, 2 Oct 2016 21:13:29 +0200 Subject: [PATCH 02/25] added __version__ (#33) Thanks @lubon --- napalm_nxos/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/napalm_nxos/__init__.py b/napalm_nxos/__init__.py index ab46743..3997c11 100644 --- a/napalm_nxos/__init__.py +++ b/napalm_nxos/__init__.py @@ -14,3 +14,11 @@ """napalm_nxos package.""" from nxos import NXOSDriver +import pkg_resources + +try: + __version__ = pkg_resources.get_distribution('napalm-nxos').version +except pkg_resources.DistributionNotFound: + __version__ = "Not installed" + +__all__= ('NXOSDriver',) From e5d82f9d172a999eb039cb9516d90472d857614f Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sun, 9 Oct 2016 18:40:58 -0700 Subject: [PATCH 03/25] Port is not passed in nxos driver Adding port as optional_argument. --- napalm_nxos/nxos.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/napalm_nxos/nxos.py b/napalm_nxos/nxos.py index 379eb8b..2aebf63 100644 --- a/napalm_nxos/nxos.py +++ b/napalm_nxos/nxos.py @@ -56,6 +56,7 @@ def __init__(self, hostname, username, password, timeout=60, optional_args=None) self.loaded = False self.fc = None self.changed = False + self.port = optional_args.get('port', 80) self.protocol = optional_args.get('nxos_protocol', 'http') def open(self): @@ -64,6 +65,7 @@ def open(self): password=self.password, ip=self.hostname, timeout=self.timeout, + port=self.port, protocol=self.protocol) self.device.show('show version', fmat='json') # execute something easy From ded9b7c7353b7cfad1a623425fba762a02e5b717 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sun, 9 Oct 2016 19:54:13 -0700 Subject: [PATCH 04/25] Add setup.cfg --- pylama.ini | 6 ------ setup.cfg | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) delete mode 100644 pylama.ini create mode 100644 setup.cfg diff --git a/pylama.ini b/pylama.ini deleted file mode 100644 index 3146a20..0000000 --- a/pylama.ini +++ /dev/null @@ -1,6 +0,0 @@ -[pylama] -linters = mccabe,pep257,pep8,pyflakes -ignore = D203, - -[pylama:pep8] -max_line_length = 120 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..ffae4a5 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,6 @@ +[pylama] +linters = mccabe,pep8,pyflakes +ignore = D203,C901 + +[pylama:pep8] +max_line_length = 100 From d8df29339afdc3b24de93b581c3efe12ce45ac94 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sun, 9 Oct 2016 19:56:39 -0700 Subject: [PATCH 05/25] Pin to napalm-base 0.17.0 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 02dcb8c..eaf9930 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -napalm-base +napalm-base==0.17.0 pycsco -PyYAML # this is a requirement for pycsco but seems to be missing +PyYAML # this is a requirement for pycsco but seems to be missing netaddr From e59bbbad30efca09d7a6c652dcbefb2590bdec07 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sun, 9 Oct 2016 20:01:28 -0700 Subject: [PATCH 06/25] Allow untrusted SSL certificates, fix HTTPS transport not working --- napalm_nxos/nxos.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/napalm_nxos/nxos.py b/napalm_nxos/nxos.py index 2aebf63..85be5db 100644 --- a/napalm_nxos/nxos.py +++ b/napalm_nxos/nxos.py @@ -20,6 +20,7 @@ import tempfile from urllib2 import URLError from datetime import datetime +import ssl # third party libs from netaddr import IPAddress @@ -36,6 +37,8 @@ from napalm_base.exceptions import ConnectionException, MergeConfigException,\ ReplaceConfigException, CommandErrorException +# Allow untrusted SSL Certificates +ssl._create_default_https_context = ssl._create_unverified_context def strip_trailing(string): lines = list(x.rstrip(' ') for x in string.splitlines()) From 8110cac699a0a3846deea95e1f5d6e26c7b547e4 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sun, 9 Oct 2016 20:04:52 -0700 Subject: [PATCH 07/25] Add requirements-dev.txt file --- requirements-dev.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 requirements-dev.txt diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..491f338 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,7 @@ +pytest +pytest-cov +pytest-json +pytest-pythonpath +pylama +flake8-import-order +-r requirements.txt From a577de05589c22d7cfa23d5da9b67476eccc3b7b Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Mon, 10 Oct 2016 20:39:41 -0700 Subject: [PATCH 08/25] Add ISSUE_TEMPLATE and PULL_REQUEST_TEMPLATE (#31) --- .github/ISSUE_TEMPLATE | 32 ++++++++++++++++++++++++++++++++ .github/PULL_REQUEST_TEMPLATE | 1 + 2 files changed, 33 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE create mode 100644 .github/PULL_REQUEST_TEMPLATE diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE new file mode 100644 index 0000000..c4809be --- /dev/null +++ b/.github/ISSUE_TEMPLATE @@ -0,0 +1,32 @@ +### Description of Issue/Question + + +### Did you follow the steps from https://github.com/napalm-automation/napalm#faq +[ ] Yes +[ ] No + + +### Setup + +### napalm-nxos version +(Paste verbatim output from `pip freeze | grep napalm-nxos` between quotes below) + +``` + +``` + +### NX-OS version +(Paste verbatim output from `show version | json` between quotes below) + +``` + +``` + +### Steps to Reproduce the Issue + +### Error Traceback +(Paste the complete traceback of the exception between quotes below) + +``` + +``` diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE new file mode 100644 index 0000000..8767eea --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE @@ -0,0 +1 @@ + From e5e56de9b39ff6c2fb2803a2bb289d0029369fc8 Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Tue, 11 Oct 2016 06:16:48 +0000 Subject: [PATCH 09/25] Some cleanup & reduce some redundancy --- napalm_nxos/nxos.py | 318 ++++++++++++++++++++------------------------ 1 file changed, 146 insertions(+), 172 deletions(-) diff --git a/napalm_nxos/nxos.py b/napalm_nxos/nxos.py index 85be5db..c7b992d 100644 --- a/napalm_nxos/nxos.py +++ b/napalm_nxos/nxos.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2015 Spotify AB. All rights reserved. # # The contents of this file are licensed under the Apache License, Version 2.0 @@ -12,34 +13,38 @@ # License for the specific language governing permissions and limitations under # the License. -# import xmltodict -# used for XML output from the API - # python stdlib import re +import ssl import tempfile from urllib2 import URLError from datetime import datetime -import ssl # third party libs from netaddr import IPAddress from netaddr.core import AddrFormatError -from pycsco.nxos.device import Device as NXOSDevice -from pycsco.nxos.utils.file_copy import FileCopy -from pycsco.nxos.utils import install_config + from pycsco.nxos.utils import nxapi_lib -from pycsco.nxos.error import DiffError, FileTransferError, CLIError +from pycsco.nxos.utils import install_config +from pycsco.nxos.utils.file_copy import FileCopy +from pycsco.nxos.device import Device as NXOSDevice + +from pycsco.nxos.error import CLIError +from pycsco.nxos.error import FileTransferError # NAPALM base import napalm_base.helpers -from napalm_base.base import NetworkDriver -from napalm_base.exceptions import ConnectionException, MergeConfigException,\ - ReplaceConfigException, CommandErrorException +from napalm_base import NetworkDriver +from napalm_base.exceptions import ConnectionException +from napalm_base.exceptions import MergeConfigException +from napalm_base.exceptions import CommandErrorException +from napalm_base.exceptions import ReplaceConfigException + # Allow untrusted SSL Certificates ssl._create_default_https_context = ssl._create_unverified_context + def strip_trailing(string): lines = list(x.rstrip(' ') for x in string.splitlines()) return '\n'.join(lines) @@ -82,6 +87,14 @@ def close(self): if self.changed: self._delete_file(self.backup_file) + @staticmethod + def _get_list_of_dicts(hsh, key): + # Helper, lookup key in hsh, return list. + result = hsh.get(key, {}) + if type(result) is dict: + result = [result] + return result + def _get_reply_body(self, result): # useful for debugging ret = result.get('ins_api', {}).get('outputs', {}).get('output', {}).get('body', {}) @@ -98,16 +111,8 @@ def _get_command_table(self, command, tablename, rowname): result = {} - try: - # xml_result = self.device.show(command) - # json_output = xmltodict.parse(xml_result[1]) - - # or directly retrive JSON - result = self.device.show(command, fmat = 'json') - json_output = eval(result[1]) - # which will converted to a plain dictionary - except Exception: - return [] + result = self.device.show(command, fmat='json') + json_output = eval(result[1]) return self._get_reply_table(json_output, tablename, rowname) @@ -194,7 +199,7 @@ def rollback(self): def get_facts(self): results = {} facts_dict = nxapi_lib.get_facts(self.device) - results['uptime'] = -1 # not implemented + results['uptime'] = -1 # not implemented results['vendor'] = unicode('Cisco') results['os_version'] = facts_dict.get('os') results['serial_number'] = unicode('N/A') @@ -217,10 +222,11 @@ def get_interfaces(self): for intf in intf_list: intf_info = nxapi_lib.get_interface(self.device, intf) formatted_info = results[intf] = {} - formatted_info['is_up'] = 'up' in intf_info.get('state', intf_info.get('admin_state', '')).lower() + formatted_info['is_up'] = 'up' in \ + intf_info.get('state', intf_info.get('admin_state', '')).lower() formatted_info['is_enabled'] = 'up' in intf_info.get('admin_state').lower() formatted_info['description'] = unicode(intf_info.get('description')) - formatted_info['last_flapped'] = -1.0 #not implemented + formatted_info['last_flapped'] = -1.0 # not implemented speed = intf_info.get('speed', '0') try: @@ -250,7 +256,6 @@ def get_lldp_neighbors(self): return results - def get_bgp_neighbors(self): cmd = 'show bgp sessions vrf all' vrf_list = self._get_command_table(cmd, 'TABLE_vrf', 'ROW_vrf') @@ -291,41 +296,36 @@ def get_bgp_neighbors(self): results[vrf_dict['vrf-name-out']] = result_vrf_dict return results - def get_checkpoint_file(self): return install_config.get_checkpoint(self.device) - def get_lldp_neighbors_detail(self, interface = ''): + def get_lldp_neighbors_detail(self, interface=''): - lldp_neighbors = dict() + lldp_neighbors = {} filter = '' if interface: - filter = 'interface {name} '.format( - name = interface - ) + filter = 'interface {name} '.format(name=interface) - command = 'show lldp neighbors {filter}detail'.format( - filter = filter - ) # seems that show LLDP neighbors detail does not return JSON output... + command = 'show lldp neighbors {filter}detail'.format(filter=filter) + # seems that show LLDP neighbors detail does not return JSON output... lldp_neighbors_table_str = self.cli([command]).get(command) # thus we need to take the raw text output - lldp_neighbors_list = lldp_neighbors_table_str.splitlines() if not lldp_neighbors_list: - return lldp_neighbors # empty dict + return lldp_neighbors # empty dict - CHASSIS_REGEX = '^(Chassis id:)\s+([a-z0-9\.]+)$' - PORT_REGEX = '^(Port id:)\s+([0-9]+)$' + CHASSIS_REGEX = '^(Chassis id:)\s+([a-z0-9\.]+)$' + PORT_REGEX = '^(Port id:)\s+([0-9]+)$' LOCAL_PORT_ID_REGEX = '^(Local Port id:)\s+(.*)$' - PORT_DESCR_REGEX = '^(Port Description:)\s+(.*)$' - SYSTEM_NAME_REGEX = '^(System Name:)\s+(.*)$' - SYSTEM_DESCR_REGEX = '^(System Description:)\s+(.*)$' - SYST_CAPAB_REEGX = '^(System Capabilities:)\s+(.*)$' - ENABL_CAPAB_REGEX = '^(Enabled Capabilities:)\s+(.*)$' - VLAN_ID_REGEX = '^(Vlan ID:)\s+(.*)$' + PORT_DESCR_REGEX = '^(Port Description:)\s+(.*)$' + SYSTEM_NAME_REGEX = '^(System Name:)\s+(.*)$' + SYSTEM_DESCR_REGEX = '^(System Description:)\s+(.*)$' + SYST_CAPAB_REEGX = '^(System Capabilities:)\s+(.*)$' + ENABL_CAPAB_REGEX = '^(Enabled Capabilities:)\s+(.*)$' + VLAN_ID_REGEX = '^(Vlan ID:)\s+(.*)$' lldp_neighbor = {} interface_name = None @@ -340,7 +340,7 @@ def get_lldp_neighbors_detail(self, interface = ''): port_rgx = re.search(PORT_REGEX, line, re.I) if port_rgx: lldp_neighbor['parent_interface'] = unicode(port_rgx.groups()[1]) - continue # jump to next line + continue # jump to next line local_port_rgx = re.search(LOCAL_PORT_ID_REGEX, line, re.I) if local_port_rgx: interface_name = local_port_rgx.groups()[1] @@ -370,61 +370,56 @@ def get_lldp_neighbors_detail(self, interface = ''): if vlan_rgx: # at the end of the loop if interface_name not in lldp_neighbors.keys(): - lldp_neighbors[interface_name] = list() + lldp_neighbors[interface_name] = [] lldp_neighbors[interface_name].append(lldp_neighbor) return lldp_neighbors - def cli(self, commands = None): + def cli(self, commands=None): - cli_output = dict() + cli_output = {} if type(commands) is not list: raise TypeError('Please enter a valid list of commands!') for command in commands: - try: - string_output = self.device.show(command, fmat = 'json', text = True)[1] - dict_output = eval(string_output) - command_output = dict_output.get('ins_api', {}).get('outputs', {}).get('output', {}).get('body', '') - cli_output[unicode(command)] = command_output - except Exception as e: - cli_output[unicode(command)] = 'Unable to execute command "{cmd}": {err}'.format( - cmd = command, - err = e - ) - raise CommandErrorException(str(cli_output)) + string_output = self.device.show(command, fmat='json', text=True)[1] + dict_output = eval(string_output) + command_output = dict_output.get('ins_api', {})\ + .get('outputs', {})\ + .get('output', {})\ + .get('body', '') + cli_output[unicode(command)] = command_output return cli_output def get_arp_table(self): - arp_table = list() + arp_table = [] command = 'show ip arp' - - arp_table_raw = self._get_command_table(command, 'TABLE_vrf', 'ROW_vrf').get('TABLE_adj', {}).get('ROW_adj', []) + arp_table_raw = self._get_command_table(command, 'TABLE_vrf', 'ROW_vrf')\ + .get('TABLE_adj', {})\ + .get('ROW_adj', []) if type(arp_table_raw) is dict: arp_table_raw = [arp_table_raw] for arp_table_entry in arp_table_raw: - ip = unicode(arp_table_entry.get('ip-addr-out')) - mac_raw = arp_table_entry.get('mac') - mac_all = mac_raw.replace('.', '').replace(':', '') - mac_format = unicode(':'.join([mac_all[i:i+2] for i in range(12)[::2]])) - age = arp_table_entry.get('time-stamp') - age_time = ''.join(age.split(':')) - age_sec = float(3600 * int(age_time[:2]) + 60 * int(age_time[2:4]) + int(age_time[4:])) - interface = unicode(arp_table_entry.get('intf-out')) - arp_table.append( - { - 'interface' : interface, - 'mac' : mac_format, - 'ip' : ip, - 'age' : age_sec - } - ) + ip = unicode(arp_table_entry.get('ip-addr-out')) + mac_raw = arp_table_entry.get('mac') + mac_all = mac_raw.replace('.', '').replace(':', '') + mac_format = unicode(':'.join([mac_all[i:i+2] for i in range(12)[::2]])) + age = arp_table_entry.get('time-stamp') + age_time = ''.join(age.split(':')) + age_sec = float(3600 * int(age_time[:2]) + 60 * int(age_time[2:4]) + int(age_time[4:])) + interface = unicode(arp_table_entry.get('intf-out')) + arp_table.append({ + 'interface': interface, + 'mac': mac_format, + 'ip': ip, + 'age': age_sec + }) return arp_table @@ -433,7 +428,6 @@ def _get_ntp_entity(self, peer_type): ntp_entities = {} command = 'show ntp peers' - ntp_peers_table = self._get_command_table(command, 'TABLE_peers', 'ROW_peers') if isinstance(ntp_peers_table, dict): @@ -457,7 +451,7 @@ def get_ntp_servers(self): def get_ntp_stats(self): - ntp_stats = list() + ntp_stats = [] command = 'show ntp peer-status' @@ -468,90 +462,75 @@ def get_ntp_stats(self): for ntp_peer in ntp_stats_table: peer_address = unicode(ntp_peer.get('remote')) - syncmode = ntp_peer.get('syncmode') - stratum = int(ntp_peer.get('st')) - hostpoll = int(ntp_peer.get('poll')) + syncmode = ntp_peer.get('syncmode') + stratum = int(ntp_peer.get('st')) + hostpoll = int(ntp_peer.get('poll')) reachability = int(ntp_peer.get('reach')) - delay = float(ntp_peer.get('delay')) + delay = float(ntp_peer.get('delay')) ntp_stats.append({ - 'remote' : peer_address, - 'synchronized' : (syncmode == '*'), - 'referenceid' : peer_address, - 'stratum' : stratum, - 'type' : u'', - 'when' : u'', - 'hostpoll' : hostpoll, - 'reachability' : reachability, - 'delay' : delay, - 'offset' : 0.0, - 'jitter' : 0.0 + 'remote': peer_address, + 'synchronized': (syncmode == '*'), + 'referenceid': peer_address, + 'stratum': stratum, + 'type': u'', + 'when': u'', + 'hostpoll': hostpoll, + 'reachability': reachability, + 'delay': delay, + 'offset': 0.0, + 'jitter': 0.0 }) return ntp_stats - def get_interfaces_ip(self): - def get_list_of_dicts(hsh, key): - # Helper, lookup key in hsh, return list. - result = hsh.get(key, {}) - if type(result) is dict: - result = [result] - return result - - def get_interfaces_data(command): - command_output = self.device.show(command, fmat = 'json') - json_output = eval(command_output[1]) - body = json_output.get('ins_api', {}).get('outputs', {}).get('output', {}).get('body', {}) - if body == '': - return [] - result = [] - for row_intf in get_list_of_dicts(body, 'TABLE_intf'): - result.extend(get_list_of_dicts(row_intf, 'ROW_intf')) - return result - - interfaces_ip = dict() + interfaces_ip = {} - ipv4_interf_table_vrf = get_interfaces_data('show ip interface') + ipv4_command = 'show ip interface' + ipv4_interf_table_vrf = self._get_command_table(ipv4_command, 'TABLE_intf', 'ROW_intf') for interface in ipv4_interf_table_vrf: interface_name = unicode(interface.get('intf-name', '')) address = unicode(interface.get('prefix', '')) - prefix = int(interface.get('masklen', '')) + prefix = int(interface.get('masklen', '')) if interface_name not in interfaces_ip.keys(): - interfaces_ip[interface_name] = dict() + interfaces_ip[interface_name] = {} if u'ipv4' not in interfaces_ip[interface_name].keys(): - interfaces_ip[interface_name][u'ipv4'] = dict() + interfaces_ip[interface_name][u'ipv4'] = {} if address not in interfaces_ip[interface_name].get(u'ipv4'): - interfaces_ip[interface_name][u'ipv4'][address] = dict() + interfaces_ip[interface_name][u'ipv4'][address] = {} interfaces_ip[interface_name][u'ipv4'][address].update({ 'prefix_length': prefix }) - secondary_addresses = interface.get('TABLE_secondary_address', {}).get('ROW_secondary_address', []) + secondary_addresses = interface.get('TABLE_secondary_address', {})\ + .get('ROW_secondary_address', []) if type(secondary_addresses) is dict: secondary_addresses = [secondary_addresses] for secondary_address in secondary_addresses: - secondary_address_ip = unicode(secondary_address.get('prefix1', '')) - secondary_address_prefix = int(secondary_address.get('masklen1', '')) + secondary_address_ip = unicode(secondary_address.get('prefix1', '')) + secondary_address_prefix = int(secondary_address.get('masklen1', '')) if u'ipv4' not in interfaces_ip[interface_name].keys(): - interfaces_ip[interface_name][u'ipv4'] = dict() + interfaces_ip[interface_name][u'ipv4'] = {} if secondary_address_ip not in interfaces_ip[interface_name].get(u'ipv4'): - interfaces_ip[interface_name][u'ipv4'][secondary_address_ip] = dict() + interfaces_ip[interface_name][u'ipv4'][secondary_address_ip] = {} interfaces_ip[interface_name][u'ipv4'][secondary_address_ip].update({ 'prefix_length': secondary_address_prefix }) - ipv6_interf_table_vrf = get_interfaces_data('show ipv6 interface') + ipv6_command = 'show ipv6 interface' + ipv6_interf_table_vrf = self._get_command_table(ipv6_command, 'TABLE_intf', 'ROW_intf') + for interface in ipv6_interf_table_vrf: interface_name = unicode(interface.get('intf-name', '')) address = unicode(interface.get('addr', '')) - prefix = int(interface.get('prefix', '').split('/')[-1]) + prefix = int(interface.get('prefix', '').split('/')[-1]) if interface_name not in interfaces_ip.keys(): - interfaces_ip[interface_name] = dict() + interfaces_ip[interface_name] = {} if u'ipv6' not in interfaces_ip[interface_name].keys(): - interfaces_ip[interface_name][u'ipv6'] = dict() + interfaces_ip[interface_name][u'ipv6'] = {} if address not in interfaces_ip[interface_name].get('ipv6'): - interfaces_ip[interface_name][u'ipv6'][address] = dict() + interfaces_ip[interface_name][u'ipv6'][address] = {} interfaces_ip[interface_name][u'ipv6'][address].update({ u'prefix_length': prefix }) @@ -560,12 +539,12 @@ def get_interfaces_data(command): secondary_addresses = [secondary_addresses] for secondary_address in secondary_addresses: sec_prefix = secondary_address.get('sec-prefix', '').split('/') - secondary_address_ip = unicode(sec_prefix[0]) - secondary_address_prefix = int(sec_prefix[-1]) + secondary_address_ip = unicode(sec_prefix[0]) + secondary_address_prefix = int(sec_prefix[-1]) if u'ipv6' not in interfaces_ip[interface_name].keys(): - interfaces_ip[interface_name][u'ipv6'] = dict() + interfaces_ip[interface_name][u'ipv6'] = {} if secondary_address_ip not in interfaces_ip[interface_name].get(u'ipv6'): - interfaces_ip[interface_name][u'ipv6'][secondary_address_ip] = dict() + interfaces_ip[interface_name][u'ipv6'][secondary_address_ip] = {} interfaces_ip[interface_name][u'ipv6'][secondary_address_ip].update({ u'prefix_length': secondary_address_prefix }) @@ -574,7 +553,7 @@ def get_interfaces_data(command): def get_mac_address_table(self): - mac_table = list() + mac_table = [] command = 'show mac address-table' mac_table_raw = self._get_command_table(command, 'TABLE_mac_address', 'ROW_mac_address') @@ -583,48 +562,44 @@ def get_mac_address_table(self): mac_table_raw = [mac_table_raw] for mac_entry in mac_table_raw: - mac_raw = mac_entry.get('disp_mac_addr') - mac_str = mac_raw.replace('.', '').replace(':', '') - mac_format = unicode(':'.join([ mac_str[i:i+2] for i in range(12)[::2] ])) - interface = unicode(mac_entry.get('disp_port')) - age = mac_entry.get('disp_age') - vlan = int(mac_entry.get('disp_vlan')) - active = True - static = (mac_entry.get('disp_is_static') != '0') - moves = 0 - last_move = 0.0 - mac_table.append( - { - 'mac' : mac_format, - 'interface' : interface, - 'vlan' : vlan, - 'active' : active, - 'static' : static, - 'moves' : moves, - 'last_move' : last_move - } - ) + mac_raw = mac_entry.get('disp_mac_addr') + mac_str = mac_raw.replace('.', '').replace(':', '') + mac_format = unicode(':'.join([mac_str[i:i+2] for i in range(12)[::2]])) + interface = unicode(mac_entry.get('disp_port')) + # age = mac_entry.get('disp_age') + vlan = int(mac_entry.get('disp_vlan')) + active = True + static = (mac_entry.get('disp_is_static') != '0') + moves = 0 + last_move = 0.0 + mac_table.append({ + 'mac': mac_format, + 'interface': interface, + 'vlan': vlan, + 'active': active, + 'static': static, + 'moves': moves, + 'last_move': last_move + }) return mac_table def get_snmp_information(self): - snmp_information = dict() + snmp_information = {} snmp_command = 'show running-config | section snmp-server' - snmp_raw_output = self.cli([snmp_command]).get(snmp_command, '') - snmp_config = napalm_base.helpers.textfsm_extractor(self, 'snmp_config', snmp_raw_output) if not snmp_config: return snmp_information snmp_information = { - 'contact' : unicode(snmp_config[0].get('contact', '')), - 'location' : unicode(snmp_config[0].get('location', '')), + 'contact': unicode(snmp_config[0].get('contact', '')), + 'location': unicode(snmp_config[0].get('location', '')), 'chassis_id': unicode(snmp_config[0].get('chassis_id', '')), - 'community' : {} + 'community': {} } for snmp_entry in snmp_config: @@ -640,10 +615,6 @@ def get_snmp_information(self): def get_users(self): - users = dict() - - command = 'sh run | sec username' - _CISCO_TO_CISCO_MAP = { 'network-admin': 15, 'network-operator': 5 @@ -655,9 +626,12 @@ def get_users(self): 'sshkeys': [] } - section_username_raw_output = self.cli([command]).get(command, '') + users = {} - section_username_tabled_output = napalm_base.helpers.textfsm_extractor(self, 'users', section_username_raw_output) + command = 'sh run | sec username' + section_username_raw_output = self.cli([command]).get(command, '') + section_username_tabled_output = napalm_base.helpers.textfsm_extractor(self, + 'users', section_username_raw_output) for user in section_username_tabled_output: username = user.get('username', '') @@ -686,7 +660,6 @@ def get_users(self): if sshkeytype and sshkeyvalue: if sshkeytype not in ['ssh-rsa', 'ssh-dsa']: continue - key = sshkeytype.replace('-', '_') users[username]['sshkeys'].append(sshkeyvalue) return users @@ -719,11 +692,12 @@ def traceroute(self, destination, source='', ttl=0, timeout=0): timeout = 5 # seconds probes = 3 # 3 probes/jop and this cannot be changed on NXOS! - version='' + version = '' try: version = '6' if IPAddress(destination).version == 6 else '' except AddrFormatError: - return {'error': 'Destination doest not look like a valid IP Address: {}'.format(destination)} + return { + 'error': 'Destination doest not look like a valid IP Address: {}'.format(destination)} source_opt = '' if source: @@ -751,7 +725,7 @@ def traceroute(self, destination, source='', ttl=0, timeout=0): hop_index = int(hop_details[0]) previous_probe_host_name = '*' previous_probe_ip_address = '*' - traceroute_result['success'][hop_index] = {'probes':{}} + traceroute_result['success'][hop_index] = {'probes': {}} for probe_index in range(probes): host_name = hop_details[3+probe_index*5] ip_address = hop_details[4+probe_index*5] From 031f431002ea854ee129dc894b61d480b588c58f Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Tue, 11 Oct 2016 06:44:31 +0000 Subject: [PATCH 10/25] Different approach for _get_reply_table --- napalm_nxos/nxos.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/napalm_nxos/nxos.py b/napalm_nxos/nxos.py index c7b992d..a4264a7 100644 --- a/napalm_nxos/nxos.py +++ b/napalm_nxos/nxos.py @@ -87,14 +87,6 @@ def close(self): if self.changed: self._delete_file(self.backup_file) - @staticmethod - def _get_list_of_dicts(hsh, key): - # Helper, lookup key in hsh, return list. - result = hsh.get(key, {}) - if type(result) is dict: - result = [result] - return result - def _get_reply_body(self, result): # useful for debugging ret = result.get('ins_api', {}).get('outputs', {}).get('output', {}).get('body', {}) @@ -105,7 +97,13 @@ def _get_reply_body(self, result): def _get_reply_table(self, result, tablename, rowname): # still useful for debugging - return self._get_reply_body(result).get(tablename, {}).get(rowname, []) + _reply_table = [] + _table = self._get_reply_body(result).get(tablename, []) + if not isinstance(_table, list): + _table = [_table] + for _row in _table: + _reply_table.append(_row.get(rowname, {})) + return _reply_table def _get_command_table(self, command, tablename, rowname): From d6da7a1d247138c8bd810c2fb9cb08a1fb577fa3 Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Tue, 11 Oct 2016 13:19:48 -0700 Subject: [PATCH 11/25] Add get_config (#38) --- napalm_nxos/nxos.py | 17 ++++++ .../nxos/mock_data/show_running-config.txt | 56 +++++++++++++++++++ .../nxos/mock_data/show_startup-config.txt | 56 +++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 test/unit/nxos/mock_data/show_running-config.txt create mode 100644 test/unit/nxos/mock_data/show_startup-config.txt diff --git a/napalm_nxos/nxos.py b/napalm_nxos/nxos.py index 85be5db..5cfa438 100644 --- a/napalm_nxos/nxos.py +++ b/napalm_nxos/nxos.py @@ -776,3 +776,20 @@ def traceroute(self, destination, source='', ttl=0, timeout=0): previous_probe_ip_address = ip_address return traceroute_result + + def get_config(self, retrieve='all'): + + config = { + 'startup': '', + 'running': '', + 'candidate': '' + } # default values + + if retrieve.lower() in ('running', 'all'): + _cmd = 'show running-config' + config['running'] = self.cli([_cmd]).get(_cmd) + if retrieve.lower() in ('startup', 'all'): + _cmd = 'show startup-config' + config['startup'] = self.cli([_cmd]).get(_cmd) + + return config diff --git a/test/unit/nxos/mock_data/show_running-config.txt b/test/unit/nxos/mock_data/show_running-config.txt new file mode 100644 index 0000000..b83a9fc --- /dev/null +++ b/test/unit/nxos/mock_data/show_running-config.txt @@ -0,0 +1,56 @@ +!Command: show startup-config +!Time: Sat May 25 15:49:57 2002 +!Startup config saved at: Sat Dec 15 09:31:54 2001 + +version 7.3(0)N1(1) +hostname sw01.sfo04 +feature lldp +feature nxapi + +banner motd #Nexus 5672 Switch +# + +interface Ethernet1/28 + +interface Ethernet1/29 + +interface Ethernet1/30 + +interface Ethernet1/31 + +interface Ethernet1/32 + +interface Ethernet1/33 + +interface Ethernet1/34 + +interface Ethernet1/35 + +interface Ethernet1/36 + +interface Ethernet1/37 + +interface Ethernet1/38 + +interface Ethernet1/39 + +interface Ethernet1/40 + +interface Ethernet1/41 + +interface Ethernet1/42 + +interface Ethernet1/43 + +interface Ethernet1/44 + +interface Ethernet1/45 + +interface Ethernet1/46 +ip igmp snooping event-history vlan-events size medium +ip igmp snooping event-history igmp-snoop-internal size small +ip igmp snooping event-history vpc size small +ip igmp snooping event-history rib size small +mac address-table notification mac-move +logging logfile sys-critical 5 size 2000000 + diff --git a/test/unit/nxos/mock_data/show_startup-config.txt b/test/unit/nxos/mock_data/show_startup-config.txt new file mode 100644 index 0000000..b83a9fc --- /dev/null +++ b/test/unit/nxos/mock_data/show_startup-config.txt @@ -0,0 +1,56 @@ +!Command: show startup-config +!Time: Sat May 25 15:49:57 2002 +!Startup config saved at: Sat Dec 15 09:31:54 2001 + +version 7.3(0)N1(1) +hostname sw01.sfo04 +feature lldp +feature nxapi + +banner motd #Nexus 5672 Switch +# + +interface Ethernet1/28 + +interface Ethernet1/29 + +interface Ethernet1/30 + +interface Ethernet1/31 + +interface Ethernet1/32 + +interface Ethernet1/33 + +interface Ethernet1/34 + +interface Ethernet1/35 + +interface Ethernet1/36 + +interface Ethernet1/37 + +interface Ethernet1/38 + +interface Ethernet1/39 + +interface Ethernet1/40 + +interface Ethernet1/41 + +interface Ethernet1/42 + +interface Ethernet1/43 + +interface Ethernet1/44 + +interface Ethernet1/45 + +interface Ethernet1/46 +ip igmp snooping event-history vlan-events size medium +ip igmp snooping event-history igmp-snoop-internal size small +ip igmp snooping event-history vpc size small +ip igmp snooping event-history rib size small +mac address-table notification mac-move +logging logfile sys-critical 5 size 2000000 + From ac773ff5fc0073b662f4621022b0b988d540b308 Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Wed, 12 Oct 2016 01:01:14 +0000 Subject: [PATCH 12/25] Refactor get_facts and get_interfaces --- napalm_nxos/nxos.py | 234 ++++--- test/unit/nxos/mock_data/show_interface.json | 636 ++++++++++++++++++ .../nxos/mock_data/show_interface_status.json | 1 + ...w_running-config___include_domain-name.txt | 1 + test/unit/nxos/mock_data/show_version.json | 1 + 5 files changed, 781 insertions(+), 92 deletions(-) create mode 100644 test/unit/nxos/mock_data/show_interface.json create mode 100644 test/unit/nxos/mock_data/show_interface_status.json create mode 100644 test/unit/nxos/mock_data/show_running-config___include_domain-name.txt create mode 100644 test/unit/nxos/mock_data/show_version.json diff --git a/napalm_nxos/nxos.py b/napalm_nxos/nxos.py index a4264a7..a122693 100644 --- a/napalm_nxos/nxos.py +++ b/napalm_nxos/nxos.py @@ -16,6 +16,7 @@ # python stdlib import re import ssl +import time import tempfile from urllib2 import URLError from datetime import datetime @@ -87,7 +88,46 @@ def close(self): if self.changed: self._delete_file(self.backup_file) - def _get_reply_body(self, result): + @staticmethod + def _compute_timestamp(stupid_cisco_output): + + if not stupid_cisco_output: + return -1.0 + + things = { + 'second(s)': { + 'weight': 1 + }, + 'minute(s)': { + 'weight': 60 + }, + 'hour(s)': { + 'weight': 3600 + }, + 'day(s)': { + 'weight': 24*3600 + }, + 'week(s)': { + 'weight': 7*24*3600 + }, + 'year(s)': { + 'weight': 365.25*24*3600 + } + } + + things_keys = things.keys() + for part in stupid_cisco_output.split(): + for key in things_keys: + if key in part: + things[key]['count'] = napalm_base.helpers.convert( + int, part.replace(key, ''), 0) + + delta = sum([det.get('count', 0)*det.get('weight') for det in things.values()]) + + return time.time() - delta + + @staticmethod + def _get_reply_body(result): # useful for debugging ret = result.get('ins_api', {}).get('outputs', {}).get('output', {}).get('body', {}) # Original 'body' entry may have been an empty string, don't return that. @@ -95,24 +135,35 @@ def _get_reply_body(self, result): return {} return ret - def _get_reply_table(self, result, tablename, rowname): - # still useful for debugging - _reply_table = [] - _table = self._get_reply_body(result).get(tablename, []) - if not isinstance(_table, list): - _table = [_table] - for _row in _table: - _reply_table.append(_row.get(rowname, {})) - return _reply_table - - def _get_command_table(self, command, tablename, rowname): + @staticmethod + def _get_table_rows(parent_table, table_name, row_name): + # because if an inconsistent piece of shit. + # {'TABLE_intf': [{'ROW_intf': { + # vs + # {'TABLE_mac_address': {'ROW_mac_address': [{ + # vs + # {'TABLE_vrf': {'ROW_vrf': {'TABLE_adj': {'ROW_adj': { + _table = parent_table.get(table_name) + _table_rows = [] + if isinstance(_table, list): + _table_rows = [_table_row.get(row_name) for _table_row in _table] + elif isinstance(_table, dict): + _table_rows = _table.get(row_name) + if not isinstance(_table_rows, list): + _table_rows = [_table_rows] + return _table_rows + + def _get_reply_table(self, result, table_name, row_name): + _table = self._get_reply_body(result) + return self._get_table_rows(_table, table_name, row_name) + + def _get_command_table(self, command, table_name, row_name): result = {} - result = self.device.show(command, fmat='json') json_output = eval(result[1]) - return self._get_reply_table(json_output, tablename, rowname) + return self._get_reply_table(json_output, table_name, row_name) def load_replace_candidate(self, filename=None, config=None): self.replace = True @@ -195,50 +246,68 @@ def rollback(self): self.changed = False def get_facts(self): - results = {} - facts_dict = nxapi_lib.get_facts(self.device) - results['uptime'] = -1 # not implemented - results['vendor'] = unicode('Cisco') - results['os_version'] = facts_dict.get('os') - results['serial_number'] = unicode('N/A') - results['model'] = facts_dict.get('platform') - results['hostname'] = facts_dict.get('hostname') - results['fqdn'] = unicode('N/A') - iface_list = results['interface_list'] = [] - - intf_dict = nxapi_lib.get_interfaces_dict(self.device) - for intf_list in intf_dict.values(): - for intf in intf_list: - iface_list.append(intf) + facts = { + 'vendor': u'Cisco' + } - return results + sh_ver_cmd = 'show version' + sh_ver_json = eval(self.device.show(sh_ver_cmd, fmat='json')[1]) + sh_ver_body = self._get_reply_body(sh_ver_json) + facts['serial_number'] = unicode(sh_ver_body.get('proc_board_id')) + facts['os_version'] = unicode(sh_ver_body.get('sys_ver_str')) + facts['uptime'] = int(time.time() - time.mktime( + datetime.strptime(sh_ver_body.get('rr_ctime', '').strip(), + '%a %b %d %H:%M:%S %Y').timetuple())) + # only an idiot would display dattime as + # Tue Jul 5 20:48:16 2016 dafuq + facts['model'] = unicode(sh_ver_body.get('chassis_id')) + host_name = unicode(sh_ver_body.get('host_name')) + facts['hostname'] = host_name + + sh_domain_cmd = 'show running-config | include domain-name' + sh_domain_name_out = self.cli([sh_domain_cmd])[sh_domain_cmd] + domain_name = '' + for line in sh_domain_name_out.splitlines(): + if line.startswith('ip domain-name'): + domain_name = line.replace('ip domain-name', '').strip() + break + + facts['fqdn'] = unicode( + '{0}.{1}'.format(host_name, domain_name) if domain_name else host_name) + + intrf_cmd = 'show interface status' + interfaces_status = self._get_command_table(intrf_cmd, 'TABLE_interface', 'ROW_interface') + facts['interface_list'] = [intrf.get('interface') for intrf in interfaces_status] + + return facts def get_interfaces(self): - results = {} - intf_dict = nxapi_lib.get_interfaces_dict(self.device) - for intf_list in intf_dict.values(): - for intf in intf_list: - intf_info = nxapi_lib.get_interface(self.device, intf) - formatted_info = results[intf] = {} - formatted_info['is_up'] = 'up' in \ - intf_info.get('state', intf_info.get('admin_state', '')).lower() - formatted_info['is_enabled'] = 'up' in intf_info.get('admin_state').lower() - formatted_info['description'] = unicode(intf_info.get('description')) - formatted_info['last_flapped'] = -1.0 # not implemented - - speed = intf_info.get('speed', '0') - try: - speed = int(re.sub(r'[^\d]', '', speed).strip()) - except ValueError: - speed = -1 + interfaces = {} + + iface_cmd = 'show interface' + interfaces_out = self._get_command_table(iface_cmd, 'TABLE_interface', 'ROW_interface') + + for interface_details in interfaces_out: + interface_name = interface_details.get('interface') + interfaces[interface_name] = { + 'is_up': (interface_details.get('admin_state', '') == 'up'), + 'is_enabled': (interface_details.get('state') == 'up') or \ + (interface_details.get('admin_state', '') == 'up'), + 'description': unicode(interface_details.get('desc', '')), + 'last_flapped': self._compute_timestamp( + interface_details.get('eth_link_flapped', '')), + 'speed': int(napalm_base.helpers.convert(int, + interface_details.get('eth_bw', [0])[0], 0) * 1e-3), + 'mac_address': napalm_base.helpers.convert( + napalm_base.helpers.mac, interface_details.get('eth_hw_addr')), - formatted_info['speed'] = speed - formatted_info['mac_address'] = unicode(intf_info.get('mac_address', 'N/A')) + } - return results + return interfaces def get_lldp_neighbors(self): results = {} + neighbor_list = nxapi_lib.get_neighbors(self.device, 'lldp') for neighbor in neighbor_list: local_iface = neighbor.get('local_interface') @@ -255,12 +324,12 @@ def get_lldp_neighbors(self): return results def get_bgp_neighbors(self): + + results = {} + cmd = 'show bgp sessions vrf all' vrf_list = self._get_command_table(cmd, 'TABLE_vrf', 'ROW_vrf') - if isinstance(vrf_list, dict): - vrf_list = [vrf_list] - results = {} for vrf_dict in vrf_list: result_vrf_dict = {} result_vrf_dict['router_id'] = unicode(vrf_dict['router-id']) @@ -270,7 +339,7 @@ def get_bgp_neighbors(self): if isinstance(neighbors_list, dict): neighbors_list = [neighbors_list] for neighbor_dict in neighbors_list: - neighborid = unicode(neighbor_dict['neighbor-id']) + neighborid = napalm_base.helpers.ip(neighbor_dict['neighbor-id']) result_peer_dict = { 'local_as': int(vrf_dict['local-as']), @@ -332,7 +401,7 @@ def get_lldp_neighbors_detail(self, interface=''): chassis_rgx = re.search(CHASSIS_REGEX, line, re.I) if chassis_rgx: lldp_neighbor = { - 'remote_chassis_id': unicode(chassis_rgx.groups()[1]) + 'remote_chassis_id': napalm_base.helpers.mac(chassis_rgx.groups()[1]) } continue port_rgx = re.search(PORT_REGEX, line, re.I) @@ -396,27 +465,22 @@ def get_arp_table(self): arp_table = [] command = 'show ip arp' - arp_table_raw = self._get_command_table(command, 'TABLE_vrf', 'ROW_vrf')\ - .get('TABLE_adj', {})\ - .get('ROW_adj', []) - - if type(arp_table_raw) is dict: - arp_table_raw = [arp_table_raw] + arp_table_vrf = self._get_command_table(command, 'TABLE_vrf', 'ROW_vrf') + arp_table_raw = self._get_table_rows(arp_table_vrf[0], 'TABLE_adj', 'ROW_adj') for arp_table_entry in arp_table_raw: - ip = unicode(arp_table_entry.get('ip-addr-out')) - mac_raw = arp_table_entry.get('mac') - mac_all = mac_raw.replace('.', '').replace(':', '') - mac_format = unicode(':'.join([mac_all[i:i+2] for i in range(12)[::2]])) + raw_ip = arp_table_entry.get('ip-addr-out') + raw_mac = arp_table_entry.get('mac') age = arp_table_entry.get('time-stamp') age_time = ''.join(age.split(':')) age_sec = float(3600 * int(age_time[:2]) + 60 * int(age_time[2:4]) + int(age_time[4:])) interface = unicode(arp_table_entry.get('intf-out')) arp_table.append({ - 'interface': interface, - 'mac': mac_format, - 'ip': ip, - 'age': age_sec + 'interface': interface, + 'mac': napalm_base.helpers.convert( + napalm_base.helpers.mac, raw_mac, raw_mac), + 'ip': napalm_base.helpers.ip(raw_ip), + 'age': age_sec }) return arp_table @@ -428,23 +492,18 @@ def _get_ntp_entity(self, peer_type): command = 'show ntp peers' ntp_peers_table = self._get_command_table(command, 'TABLE_peers', 'ROW_peers') - if isinstance(ntp_peers_table, dict): - ntp_peers_table = [ntp_peers_table] - for ntp_peer in ntp_peers_table: - if ntp_peer.get('serv_peer', '') != peer_type: + if ntp_peer.get('serv_peer', '').strip() != peer_type: continue - peer_addr = unicode(ntp_peer.get('PeerIPAddress')) + peer_addr = napalm_base.helpers.ip(ntp_peer.get('PeerIPAddress').strip()) ntp_entities[peer_addr] = {} return ntp_entities def get_ntp_peers(self): - return self._get_ntp_entity('Peer') def get_ntp_servers(self): - return self._get_ntp_entity('Server') def get_ntp_stats(self): @@ -452,14 +511,10 @@ def get_ntp_stats(self): ntp_stats = [] command = 'show ntp peer-status' - ntp_stats_table = self._get_command_table(command, 'TABLE_peersstatus', 'ROW_peersstatus') - if type(ntp_stats_table) is dict: - ntp_stats_table = [ntp_stats_table] - for ntp_peer in ntp_stats_table: - peer_address = unicode(ntp_peer.get('remote')) + peer_address = napalm_base.helpers.ip(ntp_peer.get('remote')) syncmode = ntp_peer.get('syncmode') stratum = int(ntp_peer.get('st')) hostpoll = int(ntp_peer.get('poll')) @@ -490,7 +545,7 @@ def get_interfaces_ip(self): for interface in ipv4_interf_table_vrf: interface_name = unicode(interface.get('intf-name', '')) - address = unicode(interface.get('prefix', '')) + address = napalm_base.helpers.ip(interface.get('prefix')) prefix = int(interface.get('masklen', '')) if interface_name not in interfaces_ip.keys(): interfaces_ip[interface_name] = {} @@ -506,7 +561,7 @@ def get_interfaces_ip(self): if type(secondary_addresses) is dict: secondary_addresses = [secondary_addresses] for secondary_address in secondary_addresses: - secondary_address_ip = unicode(secondary_address.get('prefix1', '')) + secondary_address_ip = napalm_base.helpers.ip(secondary_address.get('prefix1')) secondary_address_prefix = int(secondary_address.get('masklen1', '')) if u'ipv4' not in interfaces_ip[interface_name].keys(): interfaces_ip[interface_name][u'ipv4'] = {} @@ -521,7 +576,7 @@ def get_interfaces_ip(self): for interface in ipv6_interf_table_vrf: interface_name = unicode(interface.get('intf-name', '')) - address = unicode(interface.get('addr', '')) + address = napalm_base.helpers.ip(interface.get('addr', '').split('/')[0]) prefix = int(interface.get('prefix', '').split('/')[-1]) if interface_name not in interfaces_ip.keys(): interfaces_ip[interface_name] = {} @@ -537,7 +592,7 @@ def get_interfaces_ip(self): secondary_addresses = [secondary_addresses] for secondary_address in secondary_addresses: sec_prefix = secondary_address.get('sec-prefix', '').split('/') - secondary_address_ip = unicode(sec_prefix[0]) + secondary_address_ip = napalm_base.helpers.ip(sec_prefix[0]) secondary_address_prefix = int(sec_prefix[-1]) if u'ipv6' not in interfaces_ip[interface_name].keys(): interfaces_ip[interface_name][u'ipv6'] = {} @@ -556,13 +611,8 @@ def get_mac_address_table(self): command = 'show mac address-table' mac_table_raw = self._get_command_table(command, 'TABLE_mac_address', 'ROW_mac_address') - if type(mac_table_raw) is dict: - mac_table_raw = [mac_table_raw] - for mac_entry in mac_table_raw: - mac_raw = mac_entry.get('disp_mac_addr') - mac_str = mac_raw.replace('.', '').replace(':', '') - mac_format = unicode(':'.join([mac_str[i:i+2] for i in range(12)[::2]])) + raw_mac = mac_entry.get('disp_mac_addr') interface = unicode(mac_entry.get('disp_port')) # age = mac_entry.get('disp_age') vlan = int(mac_entry.get('disp_vlan')) @@ -571,7 +621,7 @@ def get_mac_address_table(self): moves = 0 last_move = 0.0 mac_table.append({ - 'mac': mac_format, + 'mac': napalm_base.helpers.mac(raw_mac), 'interface': interface, 'vlan': vlan, 'active': active, diff --git a/test/unit/nxos/mock_data/show_interface.json b/test/unit/nxos/mock_data/show_interface.json new file mode 100644 index 0000000..160da01 --- /dev/null +++ b/test/unit/nxos/mock_data/show_interface.json @@ -0,0 +1,636 @@ +{ + "ins_api": { + "outputs": { + "output": { + "msg": "Success", + "input": "show interface", + "code": "200", + "body": { + "TABLE_interface": { + "ROW_interface": [ + { + "eth_inrate1_pkts": "0", + "eth_inpause": "0", + "eth_outpkts": 0, + "eth_indiscard": "0", + "eth_crc": "0", + "eth_bia_addr": "8c60.4f73.2d28", + "eth_reset_cntr": 0, + "eth_txload": "1", + "eth_giants": 0, + "eth_outmcast": 0, + "eth_outbytes": 0, + "eth_inmcast": 0, + "eth_media": "1G", + "eth_beacon": "off", + "eth_bad_proto": "0", + "eth_mode": "access", + "share_state": "Dedicated", + "eth_hw_desc": "1000/10000 Ethernet", + "eth_outbcast": 0, + "eth_out_flowctrl": "off", + "eth_nobuf": "0", + "eth_deferred": "0", + "eth_hw_addr": "8c60.4f73.2d28", + "eth_latecoll": "0", + "eth_ratemode": "dedicated", + "eth_dly": 10, + "eth_babbles": "0", + "eth_load_interval1_rx": 30, + "eth_watchdog": "0", + "state": "down", + "eth_inrate1_bits": "0", + "eth_underrun": "0", + "eth_overrun": "0", + "eth_inerr": "0", + "eth_frame": "0", + "eth_inbcast": 0, + "eth_outerr": "0", + "eth_outdiscard": "0", + "eth_runts": 0, + "medium": "broadcast", + "eth_in_ifdown_drops": "0", + "eth_mtu": "1500", + "eth_rxload": "1", + "eth_inbytes": 0, + "eth_bw": [ + 1000000, + 1000000 + ], + "eth_outucast": 0, + "eth_outrate1_bits": "0", + "eth_speed": "1000 Mb/s", + "eth_nocarrier": "0", + "interface": "Ethernet1/1", + "eth_ethertype": "0x8100", + "eth_lostcarrier": "0", + "eth_clear_counters": "24w0d", + "eth_storm_supp": "0", + "eth_load_interval1_tx": "30", + "eth_swt_monitor": "off", + "desc": "25ldb2-eth0(nic1) ", + "eth_outpause": "0", + "eth_dribble": "0", + "eth_ignored": "0", + "eth_coll": "0", + "eth_reliability": "255", + "eth_jumbo_inpkts": "0", + "eth_inpkts": 0, + "eth_inucast": 0, + "eth_in_flowctrl": "off", + "eth_bad_eth": "0", + "eth_outrate1_pkts": "0", + "state_rsn_desc": "Link not connected", + "eth_duplex": "auto", + "eth_link_flapped": "never", + "eth_jumbo_outpkts": "0" + }, + { + "eth_inrate1_pkts": "0", + "eth_inpause": "0", + "eth_outpkts": 0, + "eth_indiscard": "0", + "eth_crc": "0", + "eth_bia_addr": "8c60.4f73.2d2a", + "eth_reset_cntr": 0, + "eth_txload": "1", + "eth_giants": 0, + "eth_outmcast": 0, + "eth_outbytes": 0, + "eth_inmcast": 0, + "eth_media": "1G", + "eth_beacon": "off", + "eth_bad_proto": "0", + "eth_mode": "access", + "share_state": "Dedicated", + "eth_hw_desc": "1000/10000 Ethernet", + "eth_outbcast": 0, + "eth_out_flowctrl": "off", + "eth_nobuf": "0", + "eth_deferred": "0", + "eth_hw_addr": "8c60.4f73.2d2a", + "eth_latecoll": "0", + "eth_ratemode": "dedicated", + "eth_dly": 10, + "eth_babbles": "0", + "eth_load_interval1_rx": 30, + "eth_watchdog": "0", + "state": "down", + "eth_inrate1_bits": "0", + "eth_underrun": "0", + "eth_overrun": "0", + "eth_inerr": "0", + "eth_frame": "0", + "eth_inbcast": 0, + "eth_outerr": "0", + "eth_outdiscard": "0", + "eth_runts": 0, + "medium": "broadcast", + "eth_in_ifdown_drops": "0", + "eth_mtu": "1500", + "eth_rxload": "1", + "eth_inbytes": 0, + "eth_bw": [ + 1000000, + 1000000 + ], + "eth_outucast": 0, + "eth_outrate1_bits": "0", + "eth_speed": "1000 Mb/s", + "eth_nocarrier": "0", + "interface": "Ethernet1/3", + "eth_ethertype": "0x8100", + "eth_lostcarrier": "0", + "eth_clear_counters": "24w0d", + "eth_storm_supp": "0", + "eth_load_interval1_tx": "30", + "eth_swt_monitor": "off", + "desc": "25ldb2-R nic1 ", + "eth_outpause": "0", + "eth_dribble": "0", + "eth_ignored": "0", + "eth_coll": "0", + "eth_reliability": "255", + "eth_jumbo_inpkts": "0", + "eth_inpkts": 0, + "eth_inucast": 0, + "eth_in_flowctrl": "off", + "eth_bad_eth": "0", + "eth_outrate1_pkts": "0", + "state_rsn_desc": "Link not connected", + "eth_duplex": "auto", + "eth_link_flapped": "never", + "eth_jumbo_outpkts": "0" + }, + { + "eth_inrate1_pkts": "0", + "eth_inpause": "0", + "eth_outpkts": 0, + "eth_indiscard": "0", + "eth_crc": "0", + "eth_bia_addr": "8c60.4f73.2d2c", + "eth_reset_cntr": 0, + "eth_txload": "1", + "eth_giants": 0, + "eth_outmcast": 0, + "eth_outbytes": 0, + "eth_inmcast": 0, + "eth_media": "1G", + "eth_beacon": "off", + "eth_bad_proto": "0", + "eth_mode": "access", + "share_state": "Dedicated", + "eth_hw_desc": "1000/10000 Ethernet", + "eth_outbcast": 0, + "eth_out_flowctrl": "off", + "eth_nobuf": "0", + "eth_deferred": "0", + "eth_hw_addr": "8c60.4f73.2d2c", + "eth_latecoll": "0", + "eth_ratemode": "dedicated", + "eth_dly": 10, + "eth_babbles": "0", + "eth_load_interval1_rx": 30, + "eth_watchdog": "0", + "state": "down", + "eth_inrate1_bits": "0", + "eth_underrun": "0", + "eth_overrun": "0", + "eth_inerr": "0", + "eth_frame": "0", + "eth_inbcast": 0, + "eth_outerr": "0", + "eth_outdiscard": "0", + "eth_runts": 0, + "medium": "broadcast", + "eth_in_ifdown_drops": "0", + "eth_mtu": "1500", + "eth_rxload": "1", + "eth_inbytes": 0, + "eth_bw": [ + 1000000, + 1000000 + ], + "eth_outucast": 0, + "eth_outrate1_bits": "0", + "eth_speed": "1000 Mb/s", + "eth_nocarrier": "0", + "interface": "Ethernet1/5", + "eth_ethertype": "0x8100", + "eth_lostcarrier": "0", + "eth_clear_counters": "24w0d", + "eth_storm_supp": "0", + "eth_load_interval1_tx": "30", + "eth_swt_monitor": "off", + "desc": "25ldb2-nic3 ", + "eth_outpause": "0", + "eth_dribble": "0", + "eth_ignored": "0", + "eth_coll": "0", + "eth_reliability": "255", + "eth_jumbo_inpkts": "0", + "eth_inpkts": 0, + "eth_inucast": 0, + "eth_in_flowctrl": "off", + "eth_bad_eth": "0", + "eth_outrate1_pkts": "0", + "state_rsn_desc": "Link not connected", + "eth_duplex": "auto", + "eth_link_flapped": "never", + "eth_jumbo_outpkts": "0" + }, + { + "eth_inrate1_pkts": "0", + "eth_inpause": "0", + "eth_outpkts": 0, + "eth_indiscard": "0", + "eth_crc": "0", + "eth_bia_addr": "8c60.4f73.2d2d", + "eth_reset_cntr": 0, + "eth_txload": "1", + "eth_giants": 0, + "eth_outmcast": 0, + "eth_outbytes": 0, + "eth_inmcast": 0, + "eth_media": "1G", + "eth_beacon": "off", + "eth_bad_proto": "0", + "eth_mode": "access", + "share_state": "Dedicated", + "eth_hw_desc": "1000/10000 Ethernet", + "eth_outbcast": 0, + "eth_out_flowctrl": "off", + "eth_nobuf": "0", + "eth_deferred": "0", + "eth_hw_addr": "8c60.4f73.2d2d", + "eth_latecoll": "0", + "eth_ratemode": "dedicated", + "eth_dly": 10, + "eth_babbles": "0", + "eth_load_interval1_rx": 30, + "eth_watchdog": "0", + "state": "down", + "eth_inrate1_bits": "0", + "eth_underrun": "0", + "eth_overrun": "0", + "eth_inerr": "0", + "eth_frame": "0", + "eth_inbcast": 0, + "eth_outerr": "0", + "eth_outdiscard": "0", + "eth_runts": 0, + "medium": "broadcast", + "eth_in_ifdown_drops": "0", + "eth_mtu": "1500", + "eth_rxload": "1", + "eth_inbytes": 0, + "eth_bw": [ + 1000000, + 1000000 + ], + "eth_outucast": 0, + "eth_outrate1_bits": "0", + "eth_speed": "1000 Mb/s", + "eth_nocarrier": "0", + "interface": "Ethernet1/6", + "eth_ethertype": "0x8100", + "eth_lostcarrier": "0", + "eth_clear_counters": "24w0d", + "eth_storm_supp": "0", + "eth_load_interval1_tx": "30", + "eth_swt_monitor": "off", + "desc": "25ldb2-nic4 ", + "eth_outpause": "0", + "eth_dribble": "0", + "eth_ignored": "0", + "eth_coll": "0", + "eth_reliability": "255", + "eth_jumbo_inpkts": "0", + "eth_inpkts": 0, + "eth_inucast": 0, + "eth_in_flowctrl": "off", + "eth_bad_eth": "0", + "eth_outrate1_pkts": "0", + "state_rsn_desc": "Link not connected", + "eth_duplex": "auto", + "eth_link_flapped": "never", + "eth_jumbo_outpkts": "0" + }, + { + "eth_inrate1_pkts": "0", + "eth_inpause": "0", + "eth_outpkts": 0, + "eth_indiscard": "0", + "eth_crc": "0", + "eth_bia_addr": "8c60.4f73.2d2f", + "eth_reset_cntr": 0, + "eth_txload": "1", + "eth_giants": 0, + "eth_outmcast": 0, + "eth_outbytes": 0, + "eth_inmcast": 0, + "eth_media": "1G", + "eth_beacon": "off", + "eth_bad_proto": "0", + "eth_mode": "access", + "share_state": "Dedicated", + "eth_hw_desc": "1000/10000 Ethernet", + "eth_outbcast": 0, + "eth_out_flowctrl": "off", + "eth_nobuf": "0", + "eth_deferred": "0", + "eth_hw_addr": "8c60.4f73.2d2f", + "eth_latecoll": "0", + "eth_ratemode": "dedicated", + "eth_dly": 10, + "eth_babbles": "0", + "eth_load_interval1_rx": 30, + "eth_watchdog": "0", + "state": "down", + "eth_inrate1_bits": "0", + "eth_underrun": "0", + "eth_overrun": "0", + "eth_inerr": "0", + "eth_frame": "0", + "eth_inbcast": 0, + "eth_outerr": "0", + "eth_outdiscard": "0", + "eth_runts": 0, + "medium": "broadcast", + "eth_in_ifdown_drops": "0", + "eth_mtu": "1500", + "eth_rxload": "1", + "eth_inbytes": 0, + "eth_bw": [ + 1000000, + 1000000 + ], + "eth_outucast": 0, + "eth_outrate1_bits": "0", + "eth_speed": "1000 Mb/s", + "eth_nocarrier": "0", + "interface": "Ethernet1/8", + "eth_ethertype": "0x8100", + "eth_lostcarrier": "0", + "eth_clear_counters": "24w0d", + "eth_storm_supp": "0", + "eth_load_interval1_tx": "30", + "eth_swt_monitor": "off", + "desc": "25ldb1-eth1 ", + "eth_outpause": "0", + "eth_dribble": "0", + "eth_ignored": "0", + "eth_coll": "0", + "eth_reliability": "255", + "eth_jumbo_inpkts": "0", + "eth_inpkts": 0, + "eth_inucast": 0, + "eth_in_flowctrl": "off", + "eth_bad_eth": "0", + "eth_outrate1_pkts": "0", + "state_rsn_desc": "Link not connected", + "eth_duplex": "auto", + "eth_link_flapped": "never", + "eth_jumbo_outpkts": "0" + }, + { + "eth_inrate1_pkts": "0", + "eth_inpause": "0", + "eth_outpkts": 0, + "eth_indiscard": "0", + "eth_crc": "0", + "eth_bia_addr": "8c60.4f73.2d31", + "eth_reset_cntr": 0, + "eth_txload": "1", + "eth_giants": 0, + "eth_outmcast": 0, + "eth_outbytes": 0, + "eth_inmcast": 0, + "eth_media": "1G", + "eth_beacon": "off", + "eth_bad_proto": "0", + "eth_mode": "access", + "share_state": "Dedicated", + "eth_hw_desc": "1000/10000 Ethernet", + "eth_outbcast": 0, + "eth_out_flowctrl": "off", + "eth_nobuf": "0", + "eth_deferred": "0", + "eth_hw_addr": "8c60.4f73.2d31", + "eth_latecoll": "0", + "eth_ratemode": "dedicated", + "eth_dly": 10, + "eth_babbles": "0", + "eth_load_interval1_rx": 30, + "eth_watchdog": "0", + "state": "down", + "eth_inrate1_bits": "0", + "eth_underrun": "0", + "eth_overrun": "0", + "eth_inerr": "0", + "eth_frame": "0", + "eth_inbcast": 0, + "eth_outerr": "0", + "eth_outdiscard": "0", + "eth_runts": 0, + "medium": "broadcast", + "eth_in_ifdown_drops": "0", + "eth_mtu": "1500", + "eth_rxload": "1", + "eth_inbytes": 0, + "eth_bw": [ + 1000000, + 1000000 + ], + "eth_outucast": 0, + "eth_outrate1_bits": "0", + "eth_speed": "1000 Mb/s", + "eth_nocarrier": "0", + "interface": "Ethernet1/10", + "eth_ethertype": "0x8100", + "eth_lostcarrier": "0", + "eth_clear_counters": "24w0d", + "eth_storm_supp": "0", + "eth_load_interval1_tx": "30", + "eth_swt_monitor": "off", + "desc": "25ldb1-nic2 ", + "eth_outpause": "0", + "eth_dribble": "0", + "eth_ignored": "0", + "eth_coll": "0", + "eth_reliability": "255", + "eth_jumbo_inpkts": "0", + "eth_inpkts": 0, + "eth_inucast": 0, + "eth_in_flowctrl": "off", + "eth_bad_eth": "0", + "eth_outrate1_pkts": "0", + "state_rsn_desc": "Link not connected", + "eth_duplex": "auto", + "eth_link_flapped": "never", + "eth_jumbo_outpkts": "0" + }, + { + "eth_inrate1_pkts": "0", + "eth_inpause": "0", + "eth_outpkts": 0, + "eth_indiscard": "0", + "eth_crc": "0", + "eth_bia_addr": "8c60.4f73.2d32", + "eth_reset_cntr": 0, + "eth_txload": "1", + "eth_giants": 0, + "eth_outmcast": 0, + "eth_outbytes": 0, + "eth_inmcast": 0, + "eth_media": "1G", + "eth_beacon": "off", + "eth_bad_proto": "0", + "eth_mode": "access", + "share_state": "Dedicated", + "eth_hw_desc": "1000/10000 Ethernet", + "eth_outbcast": 0, + "eth_out_flowctrl": "off", + "eth_nobuf": "0", + "eth_deferred": "0", + "eth_hw_addr": "8c60.4f73.2d32", + "eth_latecoll": "0", + "eth_ratemode": "dedicated", + "eth_dly": 10, + "eth_babbles": "0", + "eth_load_interval1_rx": 30, + "eth_watchdog": "0", + "state": "down", + "eth_inrate1_bits": "0", + "eth_underrun": "0", + "eth_overrun": "0", + "eth_inerr": "0", + "eth_frame": "0", + "eth_inbcast": 0, + "eth_outerr": "0", + "eth_outdiscard": "0", + "eth_runts": 0, + "medium": "broadcast", + "eth_in_ifdown_drops": "0", + "eth_mtu": "1500", + "eth_rxload": "1", + "eth_inbytes": 0, + "eth_bw": [ + 1000000, + 1000000 + ], + "eth_outucast": 0, + "eth_outrate1_bits": "0", + "eth_speed": "1000 Mb/s", + "eth_nocarrier": "0", + "interface": "Ethernet1/11", + "eth_ethertype": "0x8100", + "eth_lostcarrier": "0", + "eth_clear_counters": "24w0d", + "eth_storm_supp": "0", + "eth_load_interval1_tx": "30", + "eth_swt_monitor": "off", + "desc": "25ldb1-nic3 ", + "eth_outpause": "0", + "eth_dribble": "0", + "eth_ignored": "0", + "eth_coll": "0", + "eth_reliability": "255", + "eth_jumbo_inpkts": "0", + "eth_inpkts": 0, + "eth_inucast": 0, + "eth_in_flowctrl": "off", + "eth_bad_eth": "0", + "eth_outrate1_pkts": "0", + "state_rsn_desc": "Link not connected", + "eth_duplex": "auto", + "eth_link_flapped": "never", + "eth_jumbo_outpkts": "0" + }, + { + "eth_inrate1_pkts": "0", + "eth_inpause": "0", + "eth_outpkts": 0, + "eth_indiscard": "0", + "eth_crc": "0", + "eth_bia_addr": "8c60.4f73.2d33", + "eth_reset_cntr": 0, + "eth_txload": "1", + "eth_giants": 0, + "eth_outmcast": 0, + "eth_outbytes": 0, + "eth_inmcast": 0, + "eth_media": "1G", + "eth_beacon": "off", + "eth_bad_proto": "0", + "eth_mode": "access", + "share_state": "Dedicated", + "eth_hw_desc": "1000/10000 Ethernet", + "eth_outbcast": 0, + "eth_out_flowctrl": "off", + "eth_nobuf": "0", + "eth_deferred": "0", + "eth_hw_addr": "8c60.4f73.2d33", + "eth_latecoll": "0", + "eth_ratemode": "dedicated", + "eth_dly": 10, + "eth_babbles": "0", + "eth_load_interval1_rx": 30, + "eth_watchdog": "0", + "state": "down", + "eth_inrate1_bits": "0", + "eth_underrun": "0", + "eth_overrun": "0", + "eth_inerr": "0", + "eth_frame": "0", + "eth_inbcast": 0, + "eth_outerr": "0", + "eth_outdiscard": "0", + "eth_runts": 0, + "medium": "broadcast", + "eth_in_ifdown_drops": "0", + "eth_mtu": "1500", + "eth_rxload": "1", + "eth_inbytes": 0, + "eth_bw": [ + 1000000, + 1000000 + ], + "eth_outucast": 0, + "eth_outrate1_bits": "0", + "eth_speed": "1000 Mb/s", + "eth_nocarrier": "0", + "interface": "Ethernet1/12", + "eth_ethertype": "0x8100", + "eth_lostcarrier": "0", + "eth_clear_counters": "24w0d", + "eth_storm_supp": "0", + "eth_load_interval1_tx": "30", + "eth_swt_monitor": "off", + "desc": "25ldb1-nic4 ", + "eth_outpause": "0", + "eth_dribble": "0", + "eth_ignored": "0", + "eth_coll": "0", + "eth_reliability": "255", + "eth_jumbo_inpkts": "0", + "eth_inpkts": 0, + "eth_inucast": 0, + "eth_in_flowctrl": "off", + "eth_bad_eth": "0", + "eth_outrate1_pkts": "0", + "state_rsn_desc": "Link not connected", + "eth_duplex": "auto", + "eth_link_flapped": "never", + "eth_jumbo_outpkts": "0" + } + ] + } + } + } + }, + "version": "1.2", + "type": "cli_show", + "sid": "eoc" + } +} diff --git a/test/unit/nxos/mock_data/show_interface_status.json b/test/unit/nxos/mock_data/show_interface_status.json new file mode 100644 index 0000000..4bb60b1 --- /dev/null +++ b/test/unit/nxos/mock_data/show_interface_status.json @@ -0,0 +1 @@ +{"ins_api":{"outputs":{"output":{"msg":"Success","input":"show interface status","code":"200","body":{"TABLE_interface":{"ROW_interface":[{"name":"25ldb2-eth0(nic1) ","duplex":"full","type":"SFP-1000BASE-T","vlan":"1","state":"notconnect","interface":"Ethernet1/1","speed":"1000"},{"name":"25ldb2-eth1(nic2) ","duplex":"full","type":"SFP-1000BASE-T","vlan":"1","state":"notconnect","interface":"Ethernet1/2","speed":"1000"},{"name":"25ldb2-R nic1 ","duplex":"full","type":"SFP-1000BASE-T","vlan":"1","state":"notconnect","interface":"Ethernet1/3","speed":"1000"},{"name":"25ldb2-nic2 ","duplex":"full","type":"SFP-1000BASE-T","vlan":"1","state":"notconnect","interface":"Ethernet1/4","speed":"1000"},{"name":"25ldb2-nic3 ","duplex":"full","type":"SFP-1000BASE-T","vlan":"1","state":"notconnect","interface":"Ethernet1/5","speed":"1000"}]}}}},"version":"1.2","type":"cli_show","":{"duplex":"auto","type":"--","vlan":"routed","state":"connected","interface":"loopback0","speed":"unknown enum:<3>"},"sid":"eoc"}} diff --git a/test/unit/nxos/mock_data/show_running-config___include_domain-name.txt b/test/unit/nxos/mock_data/show_running-config___include_domain-name.txt new file mode 100644 index 0000000..97a2eab --- /dev/null +++ b/test/unit/nxos/mock_data/show_running-config___include_domain-name.txt @@ -0,0 +1 @@ +{'ins_api': {'outputs': {'output': {'msg': 'Success', 'input': 'show running-config | include domain-name', 'code': '200', 'body': 'ip domain-name fake.net\n'}}, 'version': '1.2', 'type': 'cli_show_ascii', 'sid': 'eoc'}} diff --git a/test/unit/nxos/mock_data/show_version.json b/test/unit/nxos/mock_data/show_version.json new file mode 100644 index 0000000..d12f51c --- /dev/null +++ b/test/unit/nxos/mock_data/show_version.json @@ -0,0 +1 @@ +{'ins_api': {'outputs': {'output': {'msg': 'Success', 'input': 'show version', 'code': '200', 'body': {'kern_uptm_secs': 35, 'kick_file_name': 'bootflash:///n6000-uk9-kickstart.7.3.0.N1.1.bin', 'rr_service': '', 'module_id': 'Nexus 5672UP Supervisor', 'kick_tmstmp': '02/18/2016 09:35:02', 'isan_file_name': 'bootflash:///n6000-uk9.7.3.0.N1.1.bin', 'sys_ver_str': '7.3(0)N1(1)', 'bootflash_size': 7667712, 'kickstart_ver_str': '7.3(0)N1(1)', 'kick_cmpl_time': ' 2/17/2016 22:00:00', 'chassis_id': 'Nexus 5672UP Chassis', 'proc_board_id': 'FOC19145KP1', 'memory': 8243296, 'kern_uptm_mins': 21, 'bios_ver_str': '2.1.5', 'cpu_name': 'Intel(R) Xeon(R) CPU @ 1.80', 'bios_cmpl_time': '12/03/2015', 'kern_uptm_hrs': 3, 'rr_usecs': 790714, 'isan_tmstmp': '02/18/2016 09:36:30', 'rr_sys_ver': '7.0(7)N1(1)', 'ucontroller_ver_str': 'v0.0.0.15', 'rr_reason': 'Disruptive upgrade', 'rr_ctime': ' Mon Feb 25 07:46:00 2002\n', 'header_str': 'Cisco Nexus Operating System (NX-OS) Software\nTAC support: http://www.cisco.com/tac\nDocuments: http://www.cisco.com/en/US/products/ps9372/tsd_products_support_series_home.html\nCopyright (c) 2002-2016, Cisco Systems, Inc. All rights reserved.\nThe copyrights to certain works contained herein are owned by\nother third parties and are used and distributed under license.\nSome parts of this software are covered under the GNU Public\nLicense. A copy of the license is available at\nhttp://www.gnu.org/licenses/gpl.html.\n', 'isan_cmpl_time': ' 2/17/2016 22:00:00', 'host_name': 'sw01.osl01', 'mem_type': 'kB', 'kern_uptm_days': 90, 'power_seq_ver_str': [' Module 1: v2.0.0.0', ' Module not detected']}}}, 'version': '1.2', 'type': 'cli_show', 'sid': 'eoc'}} From d63aac60d784440e730b3b57f3c781c2c7d3cc90 Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Wed, 12 Oct 2016 01:02:36 +0000 Subject: [PATCH 13/25] Use helpers --- napalm_nxos/nxos.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/napalm_nxos/nxos.py b/napalm_nxos/nxos.py index a122693..9fb98e1 100644 --- a/napalm_nxos/nxos.py +++ b/napalm_nxos/nxos.py @@ -776,7 +776,9 @@ def traceroute(self, destination, source='', ttl=0, timeout=0): traceroute_result['success'][hop_index] = {'probes': {}} for probe_index in range(probes): host_name = hop_details[3+probe_index*5] - ip_address = hop_details[4+probe_index*5] + ip_address_raw = hop_details[4+probe_index*5] + ip_address = napalm_base.helpers.convert( + napalm_base.helpers.ip, ip_address_raw, ip_address_raw) rtt = hop_details[5+probe_index*5] if rtt: rtt = float(rtt) From 7817b975ba2361e63a0cbbbdd77ddee17d3026ca Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Wed, 12 Oct 2016 03:43:32 +0000 Subject: [PATCH 14/25] Fix tests - anyway will be deprecated --- test/unit/TestNXOSDriver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/TestNXOSDriver.py b/test/unit/TestNXOSDriver.py index 65ef374..500fa11 100644 --- a/test/unit/TestNXOSDriver.py +++ b/test/unit/TestNXOSDriver.py @@ -191,7 +191,7 @@ def test_baseline_get_interfaces_ip(self): u'ipv4': {u'192.168.0.1': {'prefix_length': 24}} }, u'Vlan777': { - u'ipv6': {u'2001:db8:85a3:8d3:1319:8a2e:370:7349/64': {u'prefix_length': 64}} + u'ipv6': {u'2001:db8:85a3:8d3:1319:8a2e:370:7349': {u'prefix_length': 64}} }} self.assertEqual(actual_data, expected_data) @@ -206,7 +206,7 @@ def test_get_interfaces_ip_multiple_interfaces(self): u'ipv4': {u'10.1.2.0': {'prefix_length': 31}} }, u'Vlan777': { - u'ipv6': {u'2001:db8:85a3:8d3:1319:8a2e:370:7349/64': {u'prefix_length': 64}} + u'ipv6': {u'2001:db8:85a3:8d3:1319:8a2e:370:7349': {u'prefix_length': 64}} }} self.assertEqual(actual_data, expected_data) From 04bbaa842a1448ec171411c264bf6a148d5485dd Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Wed, 12 Oct 2016 03:57:34 +0000 Subject: [PATCH 15/25] Lint all and enable pylama --- .travis.yml | 7 +++---- napalm_nxos/__init__.py | 9 +++++++-- napalm_nxos/nxos.py | 25 ++++++++++++------------ test/unit/TestNXOSDriver.py | 38 ++++++++++++++++++++----------------- 4 files changed, 44 insertions(+), 35 deletions(-) diff --git a/.travis.yml b/.travis.yml index bd37bc7..ba0a306 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,10 +4,8 @@ python: - 2.7 install: - - pip install -r requirements.txt - - pip install . - - pip install -e git+https://github.com/napalm-automation/napalm-base.git@develop#egg=napalm-base - - pip install coveralls +- pip install -r requirements-dev.txt +- pip install . deploy: provider: pypi @@ -19,6 +17,7 @@ deploy: branch: master script: +- pylama . - cd test/unit - nosetests --with-coverage --cover-package napalm_nxos -v TestNXOSDriver:TestGetterNXOSDriver - nosetests --with-coverage --cover-package napalm_nxos -v TestNXOSDriver:TestNXOSDriver_bgp_neighbors diff --git a/napalm_nxos/__init__.py b/napalm_nxos/__init__.py index 3997c11..1882a9d 100644 --- a/napalm_nxos/__init__.py +++ b/napalm_nxos/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2016 Dravetech AB. All rights reserved. # # The contents of this file are licensed under the Apache License, Version 2.0 @@ -13,12 +14,16 @@ # the License. """napalm_nxos package.""" -from nxos import NXOSDriver + +# Import stdlib import pkg_resources +# Import local modules +from nxos import NXOSDriver # noqa + try: __version__ = pkg_resources.get_distribution('napalm-nxos').version except pkg_resources.DistributionNotFound: __version__ = "Not installed" -__all__= ('NXOSDriver',) +__all__ = ('NXOSDriver',) diff --git a/napalm_nxos/nxos.py b/napalm_nxos/nxos.py index 9fb98e1..15c4eb9 100644 --- a/napalm_nxos/nxos.py +++ b/napalm_nxos/nxos.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations under # the License. -# python stdlib +# import stdlib import re import ssl import time @@ -21,7 +21,7 @@ from urllib2 import URLError from datetime import datetime -# third party libs +# import third party lib from netaddr import IPAddress from netaddr.core import AddrFormatError @@ -33,7 +33,7 @@ from pycsco.nxos.error import CLIError from pycsco.nxos.error import FileTransferError -# NAPALM base +# import NAPALM Base import napalm_base.helpers from napalm_base import NetworkDriver from napalm_base.exceptions import ConnectionException @@ -256,7 +256,8 @@ def get_facts(self): facts['serial_number'] = unicode(sh_ver_body.get('proc_board_id')) facts['os_version'] = unicode(sh_ver_body.get('sys_ver_str')) facts['uptime'] = int(time.time() - time.mktime( - datetime.strptime(sh_ver_body.get('rr_ctime', '').strip(), + datetime.strptime( + sh_ver_body.get('rr_ctime', '').strip(), '%a %b %d %H:%M:%S %Y').timetuple())) # only an idiot would display dattime as # Tue Jul 5 20:48:16 2016 dafuq @@ -291,13 +292,13 @@ def get_interfaces(self): interface_name = interface_details.get('interface') interfaces[interface_name] = { 'is_up': (interface_details.get('admin_state', '') == 'up'), - 'is_enabled': (interface_details.get('state') == 'up') or \ - (interface_details.get('admin_state', '') == 'up'), + 'is_enabled': (interface_details.get('state') == 'up') or + (interface_details.get('admin_state', '') == 'up'), 'description': unicode(interface_details.get('desc', '')), 'last_flapped': self._compute_timestamp( interface_details.get('eth_link_flapped', '')), - 'speed': int(napalm_base.helpers.convert(int, - interface_details.get('eth_bw', [0])[0], 0) * 1e-3), + 'speed': int(napalm_base.helpers.convert( + int, interface_details.get('eth_bw', [0])[0], 0) * 1e-3), 'mac_address': napalm_base.helpers.convert( napalm_base.helpers.mac, interface_details.get('eth_hw_addr')), @@ -678,8 +679,8 @@ def get_users(self): command = 'sh run | sec username' section_username_raw_output = self.cli([command]).get(command, '') - section_username_tabled_output = napalm_base.helpers.textfsm_extractor(self, - 'users', section_username_raw_output) + section_username_tabled_output = napalm_base.helpers.textfsm_extractor( + self, 'users', section_username_raw_output) for user in section_username_tabled_output: username = user.get('username', '') @@ -744,8 +745,8 @@ def traceroute(self, destination, source='', ttl=0, timeout=0): try: version = '6' if IPAddress(destination).version == 6 else '' except AddrFormatError: - return { - 'error': 'Destination doest not look like a valid IP Address: {}'.format(destination)} + return {'error': 'Destination doest not look like a valid IP Address: {}'.format( + destination)} source_opt = '' if source: diff --git a/test/unit/TestNXOSDriver.py b/test/unit/TestNXOSDriver.py index 500fa11..de0559e 100644 --- a/test/unit/TestNXOSDriver.py +++ b/test/unit/TestNXOSDriver.py @@ -12,12 +12,17 @@ # License for the specific language governing permissions and limitations under # the License. -import unittest - -from napalm_nxos.nxos import NXOSDriver -from napalm_base.test.base import TestConfigNetworkDriver, TestGettersNetworkDriver +# import stdlib import re import os +import unittest + +# import NAPALM Base +# from napalm_base.test.base import TestConfigNetworkDriver +from napalm_base.test.base import TestGettersNetworkDriver + +# import napalm-nxos +from napalm_nxos import NXOSDriver # class TestConfigNXOSDriver(unittest.TestCase, TestConfigNetworkDriver): @@ -84,7 +89,7 @@ def test_get_bgp_neighbors_mapping(self): 'ipv4': { 'accepted_prefixes': -1, 'received_prefixes': -1, - 'sent_prefixes': -1 }}, + 'sent_prefixes': -1}}, 'description': u'', 'is_enabled': True, 'is_up': True, @@ -92,7 +97,7 @@ def test_get_bgp_neighbors_mapping(self): 'remote_as': 11111, 'remote_id': u'40.40.40.40', 'uptime': -1}} - }, # End default + }, # End default 'VRF_1': { 'router_id': u'10.10.10.10', 'peers': { @@ -120,12 +125,11 @@ def test_get_bgp_neighbors_mapping(self): 'remote_as': 22222, 'remote_id': u'30.30.30.30', 'uptime': -1}} - } # End VRF_1 + } # End VRF_1 } self.maxDiff = None self.assertEqual(actual_data, expected_data) - def test_get_bgp_neighbors_with_no_data_works(self): # TODO: verify that Nexus devices with no BGP set up return # data as given in the sample file. @@ -235,7 +239,7 @@ def set_mock_file_overrides(self, command_hash): """Set the returned data for different scenarios.""" self.data_overrides = command_hash - def get_filename(self, command, fmat = 'xml', text = False): + def get_filename(self, command, fmat='xml', text=False): if command in self.data_overrides: return self.data_overrides[command] @@ -245,25 +249,25 @@ def get_filename(self, command, fmat = 'xml', text = False): extension = 'txt' filename = re.sub(r'[\[\]\*\^\+\s\|\/]', '_', command) mock_file = '{filename}.{extension}'.format( - filename = filename[0:150], - extension = extension + filename=filename[0:150], + extension=extension ) return mock_file - def show(self, command, fmat = 'xml', text = False): + def show(self, command, fmat='xml', text=False): curr_dir = os.path.dirname(os.path.abspath(__file__)) mock_file = self.get_filename(command, fmat, text) mock_file = os.path.join(curr_dir, 'nxos', 'mock_data', mock_file) mock_data = self.read_txt_file(mock_file) if text: mock_data = { - 'ins_api':{ + 'ins_api': { 'outputs': { 'output': { - 'msg' : 'Success', - 'code' : 200, - 'input' : command, - 'body' : mock_data + 'msg': 'Success', + 'code': 200, + 'input': command, + 'body': mock_data } } } From b351bdf77488d488c1fb5239ff1b5488c6e0ebcf Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Wed, 12 Oct 2016 04:05:24 +0000 Subject: [PATCH 16/25] Docstring for _compute_timestamp --- napalm_nxos/nxos.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/napalm_nxos/nxos.py b/napalm_nxos/nxos.py index 15c4eb9..7f46c02 100644 --- a/napalm_nxos/nxos.py +++ b/napalm_nxos/nxos.py @@ -90,6 +90,11 @@ def close(self): @staticmethod def _compute_timestamp(stupid_cisco_output): + """ + Some fields such `uptime` are returned as: 23week(s) 3day(s) + This method will determine the epoch of the event. + e.g.: 23week(s) 3day(s) -> 1462248287 + """ if not stupid_cisco_output: return -1.0 From b81d0c773a1fb7ff6f292ee2e308bf1672a4d5a5 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sun, 30 Oct 2016 20:23:39 -0700 Subject: [PATCH 17/25] Fixing issues with uptime getters; fixing issue if domain-name not set --- napalm_nxos/nxos.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/napalm_nxos/nxos.py b/napalm_nxos/nxos.py index 8c95436..45573a7 100644 --- a/napalm_nxos/nxos.py +++ b/napalm_nxos/nxos.py @@ -255,23 +255,33 @@ def get_facts(self): 'vendor': u'Cisco' } + sh_uptime_cmd = 'show system uptime' + sh_uptime = eval(self.device.show(sh_uptime_cmd, fmat='json')[1]) + sh_uptime_body = self._get_reply_body(sh_uptime) + + uptime_days = sh_uptime_body.get('sys_up_days', 0) + uptime_hrs = sh_uptime_body.get('sys_up_hrs', 0) + uptime_mins = sh_uptime_body.get('sys_up_mins', 0) + uptime_secs = sh_uptime_body.get('sys_up_secs', 0) + facts['uptime'] = (uptime_secs + uptime_mins * 60 + uptime_hrs * (60 * 60) + + uptime_days * (60 * 60 * 24)) + sh_ver_cmd = 'show version' sh_ver_json = eval(self.device.show(sh_ver_cmd, fmat='json')[1]) sh_ver_body = self._get_reply_body(sh_ver_json) + + sh_ver_json = eval(self.device.show(sh_ver_cmd, fmat='json')[1]) facts['serial_number'] = unicode(sh_ver_body.get('proc_board_id')) facts['os_version'] = unicode(sh_ver_body.get('sys_ver_str')) - facts['uptime'] = int(time.time() - time.mktime( - datetime.strptime( - sh_ver_body.get('rr_ctime', '').strip(), - '%a %b %d %H:%M:%S %Y').timetuple())) - # only an idiot would display dattime as - # Tue Jul 5 20:48:16 2016 dafuq facts['model'] = unicode(sh_ver_body.get('chassis_id')) host_name = unicode(sh_ver_body.get('host_name')) facts['hostname'] = host_name sh_domain_cmd = 'show running-config | include domain-name' sh_domain_name_out = self.cli([sh_domain_cmd])[sh_domain_cmd] + if not sh_domain_name_out: + sh_domain_name_out = '' + domain_name = '' for line in sh_domain_name_out.splitlines(): if line.startswith('ip domain-name'): From 54d350f15965052c1efd1586b0a1a9732c3c93bf Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sun, 30 Oct 2016 20:38:47 -0700 Subject: [PATCH 18/25] Fixing exception error with get_bgp_neighbors if BGP not configured --- napalm_nxos/nxos.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/napalm_nxos/nxos.py b/napalm_nxos/nxos.py index 45573a7..caaf635 100644 --- a/napalm_nxos/nxos.py +++ b/napalm_nxos/nxos.py @@ -340,11 +340,12 @@ def get_lldp_neighbors(self): return results def get_bgp_neighbors(self): - results = {} - - cmd = 'show bgp sessions vrf all' - vrf_list = self._get_command_table(cmd, 'TABLE_vrf', 'ROW_vrf') + try: + cmd = 'show bgp sessions vrf all' + vrf_list = self._get_command_table(cmd, 'TABLE_vrf', 'ROW_vrf') + except CLIError: + vrf_list = [] for vrf_dict in vrf_list: result_vrf_dict = {} From d2efe1a97762adb73b4f0a744420aa4fcec657cd Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sun, 30 Oct 2016 20:49:33 -0700 Subject: [PATCH 19/25] Add mock data for show system uptime --- test/unit/nxos/mock_data/show_system_uptime.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 test/unit/nxos/mock_data/show_system_uptime.json diff --git a/test/unit/nxos/mock_data/show_system_uptime.json b/test/unit/nxos/mock_data/show_system_uptime.json new file mode 100644 index 0000000..b841a40 --- /dev/null +++ b/test/unit/nxos/mock_data/show_system_uptime.json @@ -0,0 +1,15 @@ +{'ins_api': {'outputs': {'output': {'body': {'kn_up_days': 0, + 'kn_up_hrs': 0, + 'kn_up_mins': 50, + 'kn_up_secs': 5, + 'sys_st_time': 'Mon Oct 31 02:58:31 2016', + 'sys_up_days': 0, + 'sys_up_hrs': 0, + 'sys_up_mins': 47, + 'sys_up_secs': 33}, + 'code': '200', + 'input': 'show system uptime', + 'msg': 'Success'}}, + 'sid': 'eoc', + 'type': 'cli_show', + 'version': '1.2'}} From 3575ca521c789f1a157d07258e66ae3398468015 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Mon, 31 Oct 2016 09:00:26 -0700 Subject: [PATCH 20/25] Fixing issues with get_interfaces and get_lldp --- napalm_nxos/nxos.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/napalm_nxos/nxos.py b/napalm_nxos/nxos.py index caaf635..8e51d7c 100644 --- a/napalm_nxos/nxos.py +++ b/napalm_nxos/nxos.py @@ -305,6 +305,11 @@ def get_interfaces(self): for interface_details in interfaces_out: interface_name = interface_details.get('interface') + # Earlier version of Nexus returned a list for 'eth_bw' (observed on 7.1(0)N1(1a)) + interface_speed = interface_details.get('eth_bw', 0) + if isinstance(interface_speed, list): + interface_speed = interface_speed[0] + interface_speed = int(interface_speed * 1000) interfaces[interface_name] = { 'is_up': (interface_details.get('admin_state', '') == 'up'), 'is_enabled': (interface_details.get('state') == 'up') or @@ -312,8 +317,7 @@ def get_interfaces(self): 'description': unicode(interface_details.get('desc', '')), 'last_flapped': self._compute_timestamp( interface_details.get('eth_link_flapped', '')), - 'speed': int(napalm_base.helpers.convert( - int, interface_details.get('eth_bw', [0])[0], 0) * 1e-3), + 'speed': interface_speed, 'mac_address': napalm_base.helpers.convert( napalm_base.helpers.mac, interface_details.get('eth_hw_addr')), @@ -324,7 +328,11 @@ def get_interfaces(self): def get_lldp_neighbors(self): results = {} - neighbor_list = nxapi_lib.get_neighbors(self.device, 'lldp') + try: + neighbor_list = nxapi_lib.get_neighbors(self.device, 'lldp') + except CLIError: + neighbor_list = [] + for neighbor in neighbor_list: local_iface = neighbor.get('local_interface') if neighbor.get(local_iface) is None: From e55459dfa011081d2eaa3b1650a96ac877a5691e Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Mon, 31 Oct 2016 15:47:25 -0700 Subject: [PATCH 21/25] Fixing lldp neighbor details and parent_interface (when no parent) --- napalm_nxos/nxos.py | 1 + 1 file changed, 1 insertion(+) diff --git a/napalm_nxos/nxos.py b/napalm_nxos/nxos.py index 8e51d7c..e1d213b 100644 --- a/napalm_nxos/nxos.py +++ b/napalm_nxos/nxos.py @@ -429,6 +429,7 @@ def get_lldp_neighbors_detail(self, interface=''): 'remote_chassis_id': napalm_base.helpers.mac(chassis_rgx.groups()[1]) } continue + lldp_neighbor['parent_interface'] = u'' port_rgx = re.search(PORT_REGEX, line, re.I) if port_rgx: lldp_neighbor['parent_interface'] = unicode(port_rgx.groups()[1]) From d0cb4dde4601ce1048113c11095dcbda666b5a1b Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Mon, 31 Oct 2016 15:47:25 -0700 Subject: [PATCH 22/25] Fixing lldp neighbor details and parent_interface (when no parent) --- napalm_nxos/nxos.py | 1 + 1 file changed, 1 insertion(+) diff --git a/napalm_nxos/nxos.py b/napalm_nxos/nxos.py index 8e51d7c..e1d213b 100644 --- a/napalm_nxos/nxos.py +++ b/napalm_nxos/nxos.py @@ -429,6 +429,7 @@ def get_lldp_neighbors_detail(self, interface=''): 'remote_chassis_id': napalm_base.helpers.mac(chassis_rgx.groups()[1]) } continue + lldp_neighbor['parent_interface'] = u'' port_rgx = re.search(PORT_REGEX, line, re.I) if port_rgx: lldp_neighbor['parent_interface'] = unicode(port_rgx.groups()[1]) From f605006e9773ec2c8f23ddf01f22eb593b94d63c Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Tue, 1 Nov 2016 10:37:23 +0000 Subject: [PATCH 23/25] Fix #39 --- napalm_nxos/nxos.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/napalm_nxos/nxos.py b/napalm_nxos/nxos.py index e1d213b..8c7851e 100644 --- a/napalm_nxos/nxos.py +++ b/napalm_nxos/nxos.py @@ -211,7 +211,12 @@ def compare_config(self): def _commit_merge(self): commands = self.merge_candidate.splitlines() command_string = ';'.join(list(' %s ' % x.strip() for x in commands)) - self.device.config(command_string) + self.device.config(command_string) # this will load all lines in running config only + _save_startup_cmd = 'copy run start' + copy_output = self.cli([_save_startup_cmd])[_save_startup_cmd] # exec copy run st + last_line = copy_output.splitlines()[-1] # Should be `Copy complete.` + if 'copy complete' not in last_line.lower(): # weak? + raise MergeConfigException('Unable to commit config!') def commit_config(self): if self.loaded: @@ -402,9 +407,12 @@ def get_lldp_neighbors_detail(self, interface=''): command = 'show lldp neighbors {filter}detail'.format(filter=filter) # seems that show LLDP neighbors detail does not return JSON output... - lldp_neighbors_table_str = self.cli([command]).get(command) - # thus we need to take the raw text output - lldp_neighbors_list = lldp_neighbors_table_str.splitlines() + try: + lldp_neighbors_table_str = self.cli([command]).get(command) + # thus we need to take the raw text output + lldp_neighbors_list = lldp_neighbors_table_str.splitlines() + except CLIError: + lldp_neighbors_list = [] if not lldp_neighbors_list: return lldp_neighbors # empty dict From 00b4b084d19e5b9704ab4225991dc3255f6f3ba4 Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Tue, 1 Nov 2016 10:58:44 +0000 Subject: [PATCH 24/25] Fix #46 --- napalm_nxos/nxos.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/napalm_nxos/nxos.py b/napalm_nxos/nxos.py index 8c7851e..09f6a81 100644 --- a/napalm_nxos/nxos.py +++ b/napalm_nxos/nxos.py @@ -208,15 +208,18 @@ def compare_config(self): return '' - def _commit_merge(self): - commands = self.merge_candidate.splitlines() - command_string = ';'.join(list(' %s ' % x.strip() for x in commands)) - self.device.config(command_string) # this will load all lines in running config only + def _copy_run_start(self): _save_startup_cmd = 'copy run start' copy_output = self.cli([_save_startup_cmd])[_save_startup_cmd] # exec copy run st last_line = copy_output.splitlines()[-1] # Should be `Copy complete.` if 'copy complete' not in last_line.lower(): # weak? - raise MergeConfigException('Unable to commit config!') + raise CommandErrorException('Unable to commit config!') + + def _commit_merge(self): + commands = self.merge_candidate.splitlines() + command_string = ';'.join(list(' %s ' % x.strip() for x in commands)) + self.device.config(command_string) # this will load all lines in running config only + self._copy_run_start() def commit_config(self): if self.loaded: @@ -253,6 +256,7 @@ def discard_config(self): def rollback(self): if self.changed: install_config.rollback(self.device, self.backup_file) + self._copy_run_start() self.changed = False def get_facts(self): From 77409f03a9118ca9e1c96f46a3a57d76509d877e Mon Sep 17 00:00:00 2001 From: Mircea Ulinic Date: Tue, 1 Nov 2016 11:03:01 +0000 Subject: [PATCH 25/25] Release 0.4.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b806963..03b1596 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="napalm-nxos", - version="0.3.0", + version="0.4.0", packages=find_packages(), author="David Barroso", author_email="dbarrosop@dravetech.com",