diff --git a/docs/installation/index.rst b/docs/installation/index.rst index e58ae48c3..d1f8f032a 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -14,6 +14,25 @@ You can install napalm with pip: That will install all the drivers currently available. +OS Package Managers +------------------- + +Some execution environments offer napalm through a system-level package manager. Installing with pip outside of a user profile or virtualenv/venv is inadvisable in these cases. + +FreeBSD +~~~~~~~ + +.. code-block:: bash + + pkg install net-mgmt/py-napalm + +This will install napalm and all drivers and dependencies for the default version(s) of python. To install for a specific version, python X.Y, if supported: + +.. code-block:: bash + + pkg install pyXY-napalm + + Dependencies ------------ diff --git a/docs/installation/ios.rst b/docs/installation/ios.rst index 3e5f2ed55..3138bc695 100644 --- a/docs/installation/ios.rst +++ b/docs/installation/ios.rst @@ -15,11 +15,3 @@ RedHat and CentOS .. code-block:: bash sudo yum install -y python-pip gcc openssl openssl-devel libffi-devel python-devel - - -FreeBSD -------- - -.. code-block:: bash - - sudo pkg_add -r py27-pip diff --git a/docs/installation/iosxr.rst b/docs/installation/iosxr.rst index f2fa5b039..fba72c464 100644 --- a/docs/installation/iosxr.rst +++ b/docs/installation/iosxr.rst @@ -15,11 +15,3 @@ RedHat and CentOS .. code-block:: bash sudo yum install -y python-pip gcc openssl openssl-devel libffi-devel python-devel - - -FreeBSD -------- - -.. code-block:: bash - - sudo pkg_add -r py27-pip diff --git a/docs/installation/junos.rst b/docs/installation/junos.rst index 66151bf14..64278380b 100644 --- a/docs/installation/junos.rst +++ b/docs/installation/junos.rst @@ -15,10 +15,3 @@ RedHat and CentOS .. code-block:: bash sudo yum install -y python-pip python-devel libxml2-devel libxslt-devel gcc openssl openssl-devel libffi-devel - -FreeBSD -------- - -.. code-block:: bash - - sudo pkg_add -r py27-pip libxml2 libxslt diff --git a/docs/validate/index.rst b/docs/validate/index.rst index 601862072..a7e60af22 100644 --- a/docs/validate/index.rst +++ b/docs/validate/index.rst @@ -263,7 +263,7 @@ CLI & Ansible If you prefer, you can also make use of the validate functionality via the CLI with the command ``cl_napalm_validate`` or with ansible plugin. You can find more information about them here: * CLI - https://github.com/napalm-automation/napalm/pull/168 -* Ansible - https://github.com/napalm-automation/napalm-ansible/blob/master/library/napalm_validate.py +* Ansible - https://github.com/napalm-automation/napalm-ansible/blob/master/napalm_ansible/modules/napalm_validate.py Why this and what's next diff --git a/napalm/base/base.py b/napalm/base/base.py index 3e4f15e60..1f2b94e93 100644 --- a/napalm/base/base.py +++ b/napalm/base/base.py @@ -16,14 +16,16 @@ from __future__ import print_function from __future__ import unicode_literals +import sys + +from netmiko import ConnectHandler, NetMikoTimeoutException + # local modules import napalm.base.exceptions -from napalm.base.exceptions import ConnectionException import napalm.base.helpers from napalm.base import constants as c from napalm.base import validate - -from netmiko import ConnectHandler, NetMikoTimeoutException +from napalm.base.exceptions import ConnectionException class NetworkDriver(object): @@ -44,8 +46,15 @@ def __init__(self, hostname, username, password, timeout=60, optional_args=None) raise NotImplementedError def __enter__(self): - self.open() - return self + try: + self.open() + return self + except: # noqa: E722 + # Swallow exception if __exit__ returns a True value + if self.__exit__(*sys.exc_info()): + pass + else: + raise def __exit__(self, exc_type, exc_value, exc_traceback): self.close() @@ -95,8 +104,9 @@ def _netmiko_open(self, device_type, netmiko_optional_args=None): def _netmiko_close(self): """Standardized method of closing a Netmiko connection.""" - self.device.disconnect() - self._netmiko_device = None + if getattr(self, "_netmiko_device", None): + self._netmiko_device.disconnect() + self._netmiko_device = None self.device = None def open(self): diff --git a/napalm/ios/ios.py b/napalm/ios/ios.py index 363b62e94..1dd6f4565 100644 --- a/napalm/ios/ios.py +++ b/napalm/ios/ios.py @@ -557,7 +557,7 @@ def rollback(self): 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) + self._commit_handler(cmd) # After a rollback - we no longer know whether this is configured or not. self.prompt_quiet_configured = None diff --git a/napalm/nxos/nxos.py b/napalm/nxos/nxos.py index 7952cf6ae..dee643c24 100644 --- a/napalm/nxos/nxos.py +++ b/napalm/nxos/nxos.py @@ -221,6 +221,12 @@ def commit_config(self, message=""): else: self._commit_merge() + try: + # If hostname changes ensure Netmiko state is updated properly + self._netmiko_device.set_base_prompt() + except AttributeError: + pass + self._copy_run_start() self.loaded = False else: @@ -1105,7 +1111,12 @@ def get_interfaces_ip(self): interfaces_ip[interface_name]["ipv6"] = {} if "addr" not in interface.keys(): # Handle nexus 9000 ipv6 interface output - addrs = [addr["addr"] for addr in interface["TABLE_addr"]["ROW_addr"]] + if isinstance(interface["TABLE_addr"]["ROW_addr"], list): + addrs = [ + addr["addr"] for addr in interface["TABLE_addr"]["ROW_addr"] + ] + elif isinstance(interface["TABLE_addr"]["ROW_addr"], dict): + addrs = interface["TABLE_addr"]["ROW_addr"]["addr"] interface["addr"] = addrs if type(interface.get("addr", "")) is list: diff --git a/napalm/nxos_ssh/nxos_ssh.py b/napalm/nxos_ssh/nxos_ssh.py index a43f03104..1eec8e6d7 100644 --- a/napalm/nxos_ssh/nxos_ssh.py +++ b/napalm/nxos_ssh/nxos_ssh.py @@ -449,12 +449,15 @@ def _send_command(self, command, raw_text=False): """ return self.device.send_command(command) - def _send_command_list(self, commands): + def _send_command_list(self, commands, expect_string=None): """Wrapper for Netmiko's send_command method (for list of commands.""" output = "" for command in commands: output += self.device.send_command( - command, strip_prompt=False, strip_command=False + command, + strip_prompt=False, + strip_command=False, + expect_string=expect_string, ) return output @@ -523,13 +526,15 @@ def _copy_run_start(self): raise CommandErrorException(msg) def _load_cfg_from_checkpoint(self): + commands = [ "terminal dont-ask", "rollback running-config file {}".format(self.candidate_cfg), "no terminal dont-ask", ] + try: - rollback_result = self._send_command_list(commands) + rollback_result = self._send_command_list(commands, expect_string=r"[#>]") finally: self.changed = True msg = rollback_result @@ -538,10 +543,16 @@ def _load_cfg_from_checkpoint(self): def rollback(self): if self.changed: - command = "rollback running-config file {}".format(self.rollback_cfg) - result = self._send_command(command) + commands = [ + "terminal dont-ask", + "rollback running-config file {}".format(self.rollback_cfg), + "no terminal dont-ask", + ] + result = self._send_command_list(commands, expect_string=r"[#>]") if "completed" not in result.lower(): raise ReplaceConfigException(result) + # If hostname changes ensure Netmiko state is updated properly + self._netmiko_device.set_base_prompt() self._copy_run_start() self.changed = False diff --git a/test/nxos/mocked_data/test_get_interfaces_ip/ipv6_n9k/expected_result.json b/test/nxos/mocked_data/test_get_interfaces_ip/ipv6_n9k/expected_result.json index 1f2d985d3..f88f321d4 100644 --- a/test/nxos/mocked_data/test_get_interfaces_ip/ipv6_n9k/expected_result.json +++ b/test/nxos/mocked_data/test_get_interfaces_ip/ipv6_n9k/expected_result.json @@ -4,13 +4,18 @@ "172.16.100.1": { "prefix_length": 24 } - } - }, - "Ethernet1/128": { + }, "ipv6": { - "2001:dead:beef::1": { + "200::2": { "prefix_length": 128 }, + "dead:beef::1": { + "prefix_length": 64 + } + } + }, + "Ethernet1/2": { + "ipv6": { "2001:c0ff:ee::1": { "prefix_length": 128 } diff --git a/test/nxos/mocked_data/test_get_interfaces_ip/ipv6_n9k/show_ipv6_interface.json b/test/nxos/mocked_data/test_get_interfaces_ip/ipv6_n9k/show_ipv6_interface.json index 38bc5aedc..a0ba0c439 100644 --- a/test/nxos/mocked_data/test_get_interfaces_ip/ipv6_n9k/show_ipv6_interface.json +++ b/test/nxos/mocked_data/test_get_interfaces_ip/ipv6_n9k/show_ipv6_interface.json @@ -1,64 +1,115 @@ { "TABLE_intf": { - "ROW_intf": { - "vrf-name-out": "default", - "intf-name": "Ethernet1/128", - "proto-state": "down", - "link-state": "down", - "admin-state": "up", - "prefix": "2001:dead:beef::1/128", - "linklocal-addr": "fe80::5054:ff:fe12:345d", - "linklocal-configured": "FALSE", - "mrouting-enabled": "disabled", - "mgroup-locally-joined": "TRUE", - "mtu": 1500, - "urpf-mode": "none", - "ipv6-lstype": "none", - "stats-last-reset": "never", - "upkt-fwd": 0, - "upkt-orig": 0, - "upkt-consumed": 0, - "ubyte-fwd": 0, - "ubyte-orig": 0, - "ubyte-consumed": 0, - "mpkt-fwd": 0, - "mpkt-orig": 0, - "mpkt-consumed": 0, - "mbyte-fwd": 0, - "mbyte-orig": 0, - "mbyte-consumed": 0, - "TABLE_addr": { - "ROW_addr": [ - { - "addr": "2001:dead:beef::1/128" - }, - { - "addr": "2001:c0ff:ee::1/128" - } - ] + "ROW_intf": [ + { + "vrf-name-out": "default", + "intf-name": "Ethernet1/1", + "proto-state": "up", + "link-state": "up", + "admin-state": "up", + "prefix": "dead:beef::/64", + "linklocal-addr": "fe80::a00:27ff:fe6c:ee14", + "linklocal-configured": "FALSE", + "mrouting-enabled": "disabled", + "mgroup-locally-joined": "TRUE", + "mtu": 1500, + "urpf-mode": "none", + "ipv6-lstype": "none", + "stats-last-reset": "never", + "upkt-fwd": 0, + "upkt-orig": 0, + "upkt-consumed": 0, + "ubyte-fwd": 0, + "ubyte-orig": 0, + "ubyte-consumed": 0, + "mpkt-fwd": 0, + "mpkt-orig": 9, + "mpkt-consumed": 0, + "mbyte-fwd": 0, + "mbyte-orig": 786, + "mbyte-consumed": 0, + "TABLE_addr": { + "ROW_addr": [ + { + "addr": "dead:beef::1/64" + }, + { + "addr": "200::2/128" + } + ] + }, + "TABLE_maddr": { + "ROW_maddr": [ + { + "m-addr": "ff02::1:ff00:1" + }, + { + "m-addr": "ff02::2" + }, + { + "m-addr": "ff02::1" + }, + { + "m-addr": "ff02::1:ff00:1" + }, + { + "m-addr": "ff02::1:ff6c:ee14" + } + ] + } }, - "TABLE_maddr": { - "ROW_maddr": [ - { - "m-addr": "ff02::1:ff00:1" - }, - { - "m-addr": "ff02::2" - }, - { - "m-addr": "ff02::1" - }, - { - "m-addr": "ff02::1:ff00:1" - }, - { - "m-addr": "ff02::1:ff12:345d" - }, - { - "m-addr": "ff02::1:ff00:1" + { + "vrf-name-out": "default", + "intf-name": "Ethernet1/2", + "proto-state": "up", + "link-state": "up", + "admin-state": "up", + "prefix": "2001:c0ff:ee::/128", + "linklocal-addr": "fe80::a00:27ff:fe6c:ee14", + "linklocal-configured": "FALSE", + "mrouting-enabled": "disabled", + "mgroup-locally-joined": "TRUE", + "mtu": 1500, + "urpf-mode": "none", + "ipv6-lstype": "none", + "stats-last-reset": "never", + "upkt-fwd": 0, + "upkt-orig": 0, + "upkt-consumed": 0, + "ubyte-fwd": 0, + "ubyte-orig": 0, + "ubyte-consumed": 0, + "mpkt-fwd": 0, + "mpkt-orig": 7, + "mpkt-consumed": 0, + "mbyte-fwd": 0, + "mbyte-orig": 614, + "mbyte-consumed": 0, + "TABLE_addr": { + "ROW_addr": { + "addr": "2001:c0ff:ee::1/128" } - ] + }, + "TABLE_maddr": { + "ROW_maddr": [ + { + "m-addr": "ff02::2" + }, + { + "m-addr": "ff02::1" + }, + { + "m-addr": "ff02::1:ff00:2" + }, + { + "m-addr": "ff02::1:ff6c:ee14" + }, + { + "m-addr": "ff02::1:ff00:2" + } + ] + } } - } + ] } }