diff --git a/README.md b/README.md index c23793e7d..cff8c4026 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,8 @@ Ciena SAOS Cisco Telepresence CheckPoint Gaia Enterasys -Extreme +Extreme EXOS +Extreme Wing F5 LTM Fortinet diff --git a/netmiko/__init__.py b/netmiko/__init__.py index 033ea7235..ab4ce08ed 100644 --- a/netmiko/__init__.py +++ b/netmiko/__init__.py @@ -21,7 +21,7 @@ NetmikoTimeoutError = NetMikoTimeoutException NetmikoAuthError = NetMikoAuthenticationException -__version__ = '1.4.0' +__version__ = '1.4.1' __all__ = ('ConnectHandler', 'ssh_dispatcher', 'platforms', 'SCPConn', 'FileTransfer', 'NetMikoTimeoutException', 'NetMikoAuthenticationException', diff --git a/netmiko/aruba/aruba_ssh.py b/netmiko/aruba/aruba_ssh.py index e191ca3e4..b1709b23e 100644 --- a/netmiko/aruba/aruba_ssh.py +++ b/netmiko/aruba/aruba_ssh.py @@ -1,6 +1,7 @@ """Aruba OS support""" from __future__ import unicode_literals import time +import re from netmiko.cisco_base_connection import CiscoSSHConnection @@ -22,6 +23,6 @@ def check_config_mode(self, check_string='(config) #', pattern=''): Aruba uses "() (config) #" as config prompt """ if not pattern: - pattern = self.base_prompt[:16] + pattern = re.escape(self.base_prompt[:16]) return super(ArubaSSH, self).check_config_mode(check_string=check_string, pattern=pattern) diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index 67614a052..0fcdcbcc8 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -36,7 +36,7 @@ def __init__(self, ip='', host='', username='', password='', secret='', port=Non device_type='', verbose=False, global_delay_factor=1, use_keys=False, key_file=None, allow_agent=False, ssh_strict=False, system_host_keys=False, alt_host_keys=False, alt_key_file='', ssh_config_file=None, timeout=8, - session_timeout=60): + session_timeout=60, keepalive=0): """ Initialize attributes for establishing connection to target device. @@ -85,6 +85,10 @@ def __init__(self, ip='', host='', username='', password='', secret='', port=Non :type timeout: float :param session_timeout: Set a timeout for parallel requests. :type session_timeout: float + :param keepalive: Send SSH keepalive packets at a specific interval, in seconds. + Currently defaults to 0, for backwards compatibility (it will not attempt + to keep the connection alive). + :type keepalive: int """ if ip: self.host = ip @@ -108,6 +112,7 @@ def __init__(self, ip='', host='', username='', password='', secret='', port=Non self.verbose = verbose self.timeout = timeout self.session_timeout = session_timeout + self.keepalive = keepalive # Use the greater of global_delay_factor or delay_factor local to method self.global_delay_factor = global_delay_factor @@ -221,7 +226,10 @@ def _read_channel(self): output = "" while True: if self.remote_conn.recv_ready(): - output += self.remote_conn.recv(MAX_BUFFER).decode('utf-8', 'ignore') + outbuf = self.remote_conn.recv(MAX_BUFFER) + if len(outbuf) == 0: + raise EOFError("Channel stream closed by remote device.") + output += outbuf.decode('utf-8', 'ignore') else: break elif self.protocol == 'telnet': @@ -251,7 +259,7 @@ def _read_channel_expect(self, pattern='', re_flags=0, max_loops=None): Note: this currently reads beyond pattern. In the case of SSH it reads MAX_BUFFER. In the case of telnet it reads all non-blocking data. - There are dependecies here like determining whether in config_mode that are actually + There are dependencies here like determining whether in config_mode that are actually depending on reading beyond pattern. """ debug = False @@ -271,7 +279,10 @@ def _read_channel_expect(self, pattern='', re_flags=0, max_loops=None): try: # If no data available will wait timeout seconds trying to read self._lock_netmiko_session() - new_data = self.remote_conn.recv(MAX_BUFFER).decode('utf-8', 'ignore') + new_data = self.remote_conn.recv(MAX_BUFFER) + if len(new_data) == 0: + raise EOFError("Channel stream closed by remote device.") + new_data = new_data.decode('utf-8', 'ignore') log.debug("_read_channel_expect read_data: {}".format(new_data)) output += new_data except socket.timeout: @@ -510,6 +521,8 @@ def establish_connection(self, width=None, height=None): self.remote_conn = self.remote_conn_pre.invoke_shell() self.remote_conn.settimeout(self.timeout) + if self.keepalive: + self.remote_conn.transport.set_keepalive(self.keepalive) self.special_login_handler() if self.verbose: print("Interactive SSH session established") diff --git a/netmiko/cisco/cisco_wlc_ssh.py b/netmiko/cisco/cisco_wlc_ssh.py index 2a357b270..70ac47f0e 100644 --- a/netmiko/cisco/cisco_wlc_ssh.py +++ b/netmiko/cisco/cisco_wlc_ssh.py @@ -2,6 +2,7 @@ from __future__ import print_function from __future__ import unicode_literals import time +import re from netmiko.base_connection import BaseConnection from netmiko.py23_compat import string_types @@ -109,19 +110,19 @@ def cleanup(self): def check_config_mode(self, check_string='config', pattern=''): """Checks if the device is in configuration mode or not.""" if not pattern: - pattern = self.base_prompt + pattern = re.escape(self.base_prompt) return super(CiscoWlcSSH, self).check_config_mode(check_string, pattern) def config_mode(self, config_command='config', pattern=''): """Enter into config_mode.""" if not pattern: - pattern = self.base_prompt + pattern = re.escape(self.base_prompt) return super(CiscoWlcSSH, self).config_mode(config_command, pattern) def exit_config_mode(self, exit_config='exit', pattern=''): """Exit config_mode.""" if not pattern: - pattern = self.base_prompt + pattern = re.escape(self.base_prompt) return super(CiscoWlcSSH, self).exit_config_mode(exit_config, pattern) def send_config_set(self, config_commands=None, exit_config_mode=True, delay_factor=1, diff --git a/netmiko/extreme/__init__.py b/netmiko/extreme/__init__.py index b04bd8aea..150eff194 100644 --- a/netmiko/extreme/__init__.py +++ b/netmiko/extreme/__init__.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals from netmiko.extreme.extreme_ssh import ExtremeSSH +from netmiko.extreme.extreme_wing_ssh import ExtremeWingSSH -__all__ = ['ExtremeSSH'] +__all__ = ['ExtremeSSH', 'ExtremeWingSSH'] diff --git a/netmiko/extreme/extreme_wing_ssh.py b/netmiko/extreme/extreme_wing_ssh.py new file mode 100644 index 000000000..b4f0364e6 --- /dev/null +++ b/netmiko/extreme/extreme_wing_ssh.py @@ -0,0 +1,13 @@ +from __future__ import unicode_literals + +from netmiko.cisco_base_connection import CiscoSSHConnection + + +class ExtremeWingSSH(CiscoSSHConnection): + """Extreme WiNG support.""" + def session_preparation(self): + self.set_base_prompt(pri_prompt_terminator='>', + alt_prompt_terminator='#', + delay_factor=2) + self.disable_paging(command="no page\n") + self.set_terminal_width(command='terminal width 512') diff --git a/netmiko/snmp_autodetect.py b/netmiko/snmp_autodetect.py index 75afc6d68..2779224da 100644 --- a/netmiko/snmp_autodetect.py +++ b/netmiko/snmp_autodetect.py @@ -45,6 +45,9 @@ 'cisco_xe': {"oid": ".1.3.6.1.2.1.1.1.0", "expr": re.compile(r".*IOS-XE Software,.*", re.IGNORECASE), "priority": 99}, + 'cisco_xr': {"oid": ".1.3.6.1.2.1.1.1.0", + "expr": re.compile(r".*Cisco IOS XR Software.*", re.IGNORECASE), + "priority": 99}, 'cisco_asa': {"oid": ".1.3.6.1.2.1.1.1.0", "expr": re.compile(r".*Cisco Adaptive Security Appliance.*", re.IGNORECASE), "priority": 99}, diff --git a/netmiko/ssh_dispatcher.py b/netmiko/ssh_dispatcher.py index 68bf39101..2c24e3ffe 100644 --- a/netmiko/ssh_dispatcher.py +++ b/netmiko/ssh_dispatcher.py @@ -24,6 +24,7 @@ from netmiko.ovs import OvsLinuxSSH from netmiko.enterasys import EnterasysSSH from netmiko.extreme import ExtremeSSH +from netmiko.extreme import ExtremeWingSSH from netmiko.alcatel import AlcatelSrosSSH from netmiko.dell import DellForce10SSH from netmiko.dell import DellPowerConnectSSH @@ -72,6 +73,7 @@ 'ovs_linux': OvsLinuxSSH, 'enterasys': EnterasysSSH, 'extreme': ExtremeSSH, + 'extreme_wing': ExtremeWingSSH, 'alcatel_sros': AlcatelSrosSSH, 'fortinet': FortinetSSH, 'checkpoint_gaia': CheckPointGaiaSSH, diff --git a/netmiko/utilities.py b/netmiko/utilities.py index 6d25d3ddf..f45dcaa2c 100644 --- a/netmiko/utilities.py +++ b/netmiko/utilities.py @@ -11,6 +11,7 @@ 'juniper': 'show configuration', 'juniper_junos': 'show configuration', 'extreme': 'show configuration', + 'extreme_wing': 'show running-config', 'hp_comware': 'display current-configuration', 'huawei': 'display current-configuration', 'fortinet': 'show full-configuration', diff --git a/release_process.txt b/release_process.txt index 59d38bdc4..4d2a7525d 100644 --- a/release_process.txt +++ b/release_process.txt @@ -1,5 +1,7 @@ # Use pynetcio machine +# Make sure you have rolled the version in __init__.py + # pylama code (from ./netmiko) pylama diff --git a/tests/test_iosxe.sh b/tests/test_iosxe.sh new file mode 100755 index 000000000..53a44f7f0 --- /dev/null +++ b/tests/test_iosxe.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +RETURN_CODE=0 + +echo "Starting tests...good luck:" \ +&& echo "Cisco IOS-XE" \ +&& py.test -v test_netmiko_show.py --test_device ios_xe \ +&& py.test -v test_netmiko_config.py --test_device ios_xe \ +|| RETURN_CODE=1 + +exit $RETURN_CODE diff --git a/tests/test_suite_alt.sh b/tests/test_suite_alt.sh index 5ab0bb7b1..e5d00dce4 100755 --- a/tests/test_suite_alt.sh +++ b/tests/test_suite_alt.sh @@ -39,10 +39,6 @@ echo "Starting tests...good luck:" \ && py.test -v test_netmiko_show.py --test_device hp_procurve \ && py.test -v test_netmiko_config.py --test_device hp_procurve \ \ -&& echo "HP Comware7" \ -&& py.test -v test_netmiko_show.py --test_device hp_comware \ -&& py.test -v test_netmiko_config.py --test_device hp_comware \ -\ && echo "Juniper" \ && py.test -v test_netmiko_show.py --test_device juniper_srx \ && py.test -v test_netmiko_config.py --test_device juniper_srx \ @@ -74,3 +70,9 @@ exit $RETURN_CODE #&& echo "Cisco NXOS" \ #&& py.test -v test_netmiko_show.py --test_device nxos1 \ #&& py.test -v test_netmiko_config.py --test_device nxos1 \ + + +#&& echo "HP Comware7" \ +#&& py.test -v test_netmiko_show.py --test_device hp_comware \ +#&& py.test -v test_netmiko_config.py --test_device hp_comware \ +#\