From 290aedea90e66f7f9aebcec1ffb072ebefb3b5dc Mon Sep 17 00:00:00 2001 From: broncofan Date: Wed, 22 Mar 2017 12:38:04 -0500 Subject: [PATCH 01/32] Add support for Dell PowerConnect where Telnet is used. Specifically tested with Dell 8024 --- netmiko/dell/__init__.py | 3 +- netmiko/dell/dell_powerconnect_telnet.py | 71 ++++++++++++++++++++++++ netmiko/ssh_dispatcher.py | 4 +- 3 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 netmiko/dell/dell_powerconnect_telnet.py diff --git a/netmiko/dell/__init__.py b/netmiko/dell/__init__.py index d9dd7c4cd..15868b82b 100644 --- a/netmiko/dell/__init__.py +++ b/netmiko/dell/__init__.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals from netmiko.dell.dell_force10_ssh import DellForce10SSH from netmiko.dell.dell_powerconnect_ssh import DellPowerConnectSSH +from netmiko.dell.dell_powerconnect_telnet import DellPowerConnectTelnet -__all__ = ['DellForce10SSH', 'DellPowerConnectSSH'] +__all__ = ['DellForce10SSH', 'DellPowerConnectSSH', 'DellPowerConnectTelnet'] diff --git a/netmiko/dell/dell_powerconnect_telnet.py b/netmiko/dell/dell_powerconnect_telnet.py new file mode 100644 index 000000000..4f728e3f6 --- /dev/null +++ b/netmiko/dell/dell_powerconnect_telnet.py @@ -0,0 +1,71 @@ +"""Dell Telnet Driver.""" +from __future__ import unicode_literals +from netmiko.cisco_base_connection import CiscoSSHConnection + +import time,re + +class DellPowerConnectTelnet(CiscoSSHConnection): + def disable_paging(self, command="terminal length 0", delay_factor=1): + print ("MADE IT HERE:") + """Must be in enable mode to disable paging.""" + self.enable() + debug = True + delay_factor = self.select_delay_factor(delay_factor) + time.sleep(delay_factor * .1) + self.clear_buffer() + command = self.normalize_cmd(command) + if debug: + print("In disable_paging") + print("Command: {}".format(command)) + self.write_channel(command) + output = self.read_until_prompt() + if self.ansi_escape_codes: + output = self.strip_ansi_escape_codes(output) + if debug: + print(output) + print("Exiting disable_paging") + return output + + def telnet_login(self, pri_prompt_terminator='#', alt_prompt_terminator='>', + username_pattern=r"User:", pwd_pattern=r"assword", + delay_factor=1, max_loops=60): + """Telnet login. Can be username/password or just password.""" + TELNET_RETURN = '\r\n' + + delay_factor = self.select_delay_factor(delay_factor) + time.sleep(1 * delay_factor) + + output = '' + return_msg = '' + i = 1 + while i <= max_loops: + try: + output = self.read_channel() + return_msg += output + + # Search for username pattern / send username + if re.search(username_pattern, output): + self.write_channel(self.username + TELNET_RETURN) + time.sleep(1 * delay_factor) + output = self.read_channel() + return_msg += output + + # Search for password pattern / send password + if re.search(pwd_pattern, output): + self.write_channel(self.password + TELNET_RETURN) + time.sleep(.5 * delay_factor) + output = self.read_channel() + return_msg += output + if pri_prompt_terminator in output or alt_prompt_terminator in output: + return return_msg + + # Check if proper data received + if pri_prompt_terminator in output or alt_prompt_terminator in output: + return return_msg + + self.write_channel(TELNET_RETURN) + time.sleep(.5 * delay_factor) + i += 1 + except EOFError: + msg = "Telnet login failed: {0}".format(self.host) + raise NetMikoAuthenticationException(msg) diff --git a/netmiko/ssh_dispatcher.py b/netmiko/ssh_dispatcher.py index ae4079b06..19bfa678b 100644 --- a/netmiko/ssh_dispatcher.py +++ b/netmiko/ssh_dispatcher.py @@ -26,6 +26,7 @@ from netmiko.alcatel import AlcatelSrosSSH from netmiko.dell import DellForce10SSH from netmiko.dell import DellPowerConnectSSH +from netmiko.dell import DellPowerConnectTelnet from netmiko.paloalto import PaloAltoPanosSSH from netmiko.quanta import QuantaMeshSSH from netmiko.aruba import ArubaSSH @@ -74,6 +75,7 @@ 'fortinet': FortinetSSH, 'dell_force10': DellForce10SSH, 'dell_powerconnect': DellPowerConnectSSH, + 'dell_powerconnect_telnet': DellPowerConnectTelnet, 'paloalto_panos': PaloAltoPanosSSH, 'quanta_mesh': QuantaMeshSSH, 'aruba_os': ArubaSSH, @@ -134,4 +136,4 @@ def redispatch(obj, device_type, session_prep=True): obj.device_type = device_type obj.__class__ = new_class if session_prep: - obj.session_preparation() \ No newline at end of file + obj.session_preparation() From f89e9ae561a347f2a23320f7a458828ba6a533d5 Mon Sep 17 00:00:00 2001 From: broncofan Date: Thu, 23 Mar 2017 16:20:42 -0500 Subject: [PATCH 02/32] Fixed some PEP8 issues. --- netmiko/dell/dell_powerconnect_telnet.py | 10 +++++++--- netmiko/mellanox/mellanox_ssh.py | 4 +++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/netmiko/dell/dell_powerconnect_telnet.py b/netmiko/dell/dell_powerconnect_telnet.py index 4f728e3f6..6b80bc56a 100644 --- a/netmiko/dell/dell_powerconnect_telnet.py +++ b/netmiko/dell/dell_powerconnect_telnet.py @@ -1,14 +1,17 @@ """Dell Telnet Driver.""" from __future__ import unicode_literals from netmiko.cisco_base_connection import CiscoSSHConnection +from netmiko.ssh_exception import NetMikoAuthenticationException + +import time +import re -import time,re class DellPowerConnectTelnet(CiscoSSHConnection): def disable_paging(self, command="terminal length 0", delay_factor=1): - print ("MADE IT HERE:") + print("MADE IT HERE:") """Must be in enable mode to disable paging.""" - self.enable() + self.enable() debug = True delay_factor = self.select_delay_factor(delay_factor) time.sleep(delay_factor * .1) @@ -26,6 +29,7 @@ def disable_paging(self, command="terminal length 0", delay_factor=1): print("Exiting disable_paging") return output + def telnet_login(self, pri_prompt_terminator='#', alt_prompt_terminator='>', username_pattern=r"User:", pwd_pattern=r"assword", delay_factor=1, max_loops=60): diff --git a/netmiko/mellanox/mellanox_ssh.py b/netmiko/mellanox/mellanox_ssh.py index ed1205eda..3f870c044 100644 --- a/netmiko/mellanox/mellanox_ssh.py +++ b/netmiko/mellanox/mellanox_ssh.py @@ -3,6 +3,7 @@ from netmiko.cisco_base_connection import CiscoSSHConnection import time + class MellanoxSSH(CiscoSSHConnection): def config_mode(self, config_command='config term', pattern='#'): @@ -17,7 +18,7 @@ def config_mode(self, config_command='config term', pattern='#'): def check_config_mode(self, check_string='(config)', pattern=r'[>|#]'): return super(MellanoxSSH, self).check_config_mode(check_string=check_string, - pattern=pattern) + pattern=pattern) def disable_paging(self, command="terminal length 999", delay_factor=1): """Disable paging default to a Cisco CLI method.""" @@ -38,6 +39,7 @@ def disable_paging(self, command="terminal length 999", delay_factor=1): print("Exiting disable_paging") return output + def exit_config_mode(self, exit_config='exit', pattern='#'): """Exit from configuration mode.""" debug = False From c072d4f70102808e4c02eba32c05d5c6bb378c0e Mon Sep 17 00:00:00 2001 From: broncofan Date: Thu, 23 Mar 2017 16:24:01 -0500 Subject: [PATCH 03/32] Fixed some PEP8 issues. --- netmiko/dell/dell_powerconnect_telnet.py | 1 - netmiko/mellanox/mellanox_ssh.py | 1 - 2 files changed, 2 deletions(-) diff --git a/netmiko/dell/dell_powerconnect_telnet.py b/netmiko/dell/dell_powerconnect_telnet.py index 6b80bc56a..705f27076 100644 --- a/netmiko/dell/dell_powerconnect_telnet.py +++ b/netmiko/dell/dell_powerconnect_telnet.py @@ -29,7 +29,6 @@ def disable_paging(self, command="terminal length 0", delay_factor=1): print("Exiting disable_paging") return output - def telnet_login(self, pri_prompt_terminator='#', alt_prompt_terminator='>', username_pattern=r"User:", pwd_pattern=r"assword", delay_factor=1, max_loops=60): diff --git a/netmiko/mellanox/mellanox_ssh.py b/netmiko/mellanox/mellanox_ssh.py index 3f870c044..762d6c5a9 100644 --- a/netmiko/mellanox/mellanox_ssh.py +++ b/netmiko/mellanox/mellanox_ssh.py @@ -39,7 +39,6 @@ def disable_paging(self, command="terminal length 999", delay_factor=1): print("Exiting disable_paging") return output - def exit_config_mode(self, exit_config='exit', pattern='#'): """Exit from configuration mode.""" debug = False From f1add7294ea25a59c267ea14b8232976319ba60b Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sat, 25 Mar 2017 08:51:30 -0700 Subject: [PATCH 04/32] Improvements for pydoc --- netmiko/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/netmiko/__init__.py b/netmiko/__init__.py index e97075d7a..25ed8b62c 100644 --- a/netmiko/__init__.py +++ b/netmiko/__init__.py @@ -15,17 +15,18 @@ from netmiko.ssh_exception import NetMikoTimeoutException from netmiko.ssh_exception import NetMikoAuthenticationException from netmiko.ssh_autodetect import SSHDetect +from netmiko.base_connection import BaseConnection # Alternate naming NetmikoTimeoutError = NetMikoTimeoutException NetmikoAuthError = NetMikoAuthenticationException -__version__ = '1.3.0' +__version__ = '1.3.1' __all__ = ('ConnectHandler', 'ssh_dispatcher', 'platforms', 'SCPConn', 'FileTransfer', 'NetMikoTimeoutException', 'NetMikoAuthenticationException', 'NetmikoTimeoutError', 'NetmikoAuthError', 'InLineTransfer', 'redispatch', - 'SSHDetect') + 'SSHDetect', 'BaseConnection') # Cisco cntl-shift-six sequence CNTL_SHIFT_6 = chr(30) From 0d26ffc0cfeb1c9ec2ef29d681fcf6cc92fa8e42 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sat, 25 Mar 2017 09:49:14 -0700 Subject: [PATCH 05/32] Testing readthedocs --- netmiko/base_connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index 7abc2c4a0..f3ec9c5f8 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -156,7 +156,7 @@ def __init__(self, ip='', host='', username='', password='', secret='', port=Non self.clear_buffer() def __enter__(self): - """Enter runtime context""" + """Establish a session using a Context Manager.""" return self def __exit__(self, exc_type, exc_value, traceback): From 1acc3f496074e9985e8bfd522ca8fce7edc9b6d8 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sat, 25 Mar 2017 09:57:05 -0700 Subject: [PATCH 06/32] Update __init__ documentation. --- netmiko/base_connection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index f3ec9c5f8..bd09fe812 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -58,9 +58,9 @@ def __init__(self, ip='', host='', username='', password='', secret='', port=Non :type port: int or None :param device_type: Class selection based on device type. :type device_type: str - :param verbose: If `True` enables more verbose logging. + :param verbose: Enable additional messages to standard output. :type verbose: bool - :param global_delay_factor: Controls global delay factor value. + :param global_delay_factor: Multiplication factor affecting Netmiko delays (default: 1). :type global_delay_factor: int :param use_keys: If true, Paramiko will attempt to connect to target device using SSH keys. @@ -160,7 +160,7 @@ def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): - """Gracefully close connection on context manager exit""" + """Gracefully close connection on Context Manager exit.""" self.disconnect() if exc_type is not None: raise exc_type(exc_value) From 86f89930fc075cf25f00e782ddd09dbca3de5aa4 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sat, 25 Mar 2017 10:13:56 -0700 Subject: [PATCH 07/32] Improving readthedocs __init__ definition. --- netmiko/base_connection.py | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index bd09fe812..c73d4ab18 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -62,36 +62,27 @@ def __init__(self, ip='', host='', username='', password='', secret='', port=Non :type verbose: bool :param global_delay_factor: Multiplication factor affecting Netmiko delays (default: 1). :type global_delay_factor: int - :param use_keys: If true, Paramiko will attempt to connect to - target device using SSH keys. + :param use_keys: Connect to target device using SSH keys. :type use_keys: bool - :param key_file: Name of the SSH key file to use for Paramiko - SSH connection authentication. + :param key_file: Filename path of the SSH key file to use. :type key_file: str - :param allow_agent: Set to True to enable connect to the SSH agent + :param allow_agent: Enable use of SSH key-agent. :type allow_agent: bool - :param ssh_strict: If `True` Paramiko will automatically reject - unknown hostname and keys. If 'False' Paramiko will - automatically add the hostname and new host key. + :param ssh_strict: Automatically reject unknown SSH host keys (default: False, which + means unknown SSH host keys will be accepted). :type ssh_strict: bool - :param system_host_keys: If `True` Paramiko will load host keys - from the user's local 'known hosts' file. + :param system_host_keys: Load host keys from the user's 'known_hosts' file. :type system_host_keys: bool - :param alt_host_keys: If `True` host keys will be loaded from - a local host-key file. + :param alt_host_keys: If `True` host keys will be loaded from the file specified in + 'alt_key_file'. :type alt_host_keys: bool - :param alt_key_file: If `alt_host_keys` is set to `True`, provide - the filename of the local host-key file to load. + :param alt_key_file: SSH host key file to use (if alt_host_keys=True). :type alt_key_file: str - :param ssh_config_file: File name of a OpenSSH configuration file - to load SSH connection parameters from. + :param ssh_config_file: File name of OpenSSH configuration file. :type ssh_config_file: str - :param timeout: Set a timeout on blocking read/write operations. + :param timeout: Connection timeout. :type timeout: float - :param session_timeout: Set a timeout for parallel requests. When - the channel is busy serving other tasks and the queue is - very long, in case the wait time is higher than this value, - NetMikoTimeoutException will be raised. + :param session_timeout: Set a timeout for parallel requests. :type session_timeout: float """ if ip: From 5d69933fa749afc80bc06ce33d0392ddf9c6b44d Mon Sep 17 00:00:00 2001 From: Elisa Jasinska Date: Sat, 25 Mar 2017 17:29:19 +0100 Subject: [PATCH 08/32] add extreme_ssh driver specifics --- netmiko/extreme/extreme_ssh.py | 114 ++++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/netmiko/extreme/extreme_ssh.py b/netmiko/extreme/extreme_ssh.py index 2ef0465d0..2e6373234 100644 --- a/netmiko/extreme/extreme_ssh.py +++ b/netmiko/extreme/extreme_ssh.py @@ -1,13 +1,123 @@ """Extreme support.""" from __future__ import unicode_literals +import re +import time from netmiko.cisco_base_connection import CiscoSSHConnection +QUESTIONS = { + 'save': 'Do you want to save configuration to', +} + + class ExtremeSSH(CiscoSSHConnection): """Extreme support.""" + def session_preparation(self): - """Extreme requires enable mode to disable paging.""" + """ + Extreme requires enable mode to disable paging. + """ self._test_channel_read() - self.enable() self.set_base_prompt() self.disable_paging(command="disable clipaging\n") + + def answer_questions(self, *args, **kwargs): + """ + Send questions/answers with send_command_timing. + """ + if len(args) >= 1: + command_string = args[0] + + # handle commands which return a question + output = self.send_command_timing(command_string) + if QUESTIONS[command_string] in output: + output += self.send_command_timing('yes') + + return output + + def send_command(self, *args, **kwargs): + """ + Catch questions from the CLI. + Handle prompt changes during command executions. + """ + if len(args) >= 1: + command_string = args[0] + + # command that prompt a question + if command_string in QUESTIONS.keys(): + output = self.answer_questions(command_string) + + # all other commands + else: + # Turn off 'auto_find_prompt' which loses the striped base prompt + kwargs.update({'auto_find_prompt': False}) + # refresh prompt, which could have been changed on previous command + self.set_base_prompt() + output = super(ExtremeSSH, self).send_command(*args, **kwargs) + + return output + + def send_config_set(self, config_commands=None, exit_config_mode=True, delay_factor=1, + max_loops=150, strip_prompt=False, strip_command=False): + """ + Overwrite send_config_set to acomodate for commands which return a question + and expect a reply. + """ + delay_factor = self.select_delay_factor(delay_factor) + if config_commands is None: + return '' + if not hasattr(config_commands, '__iter__'): + raise ValueError("Invalid argument passed into send_config_set") + + # Send config commands + output = '' + for command_string in config_commands: + + # command that prompt a question + if command_string in QUESTIONS.keys(): + output += self.answer_questions(command_string) + + # all other commands + else: + self.write_channel(self.normalize_cmd(command_string)) + time.sleep(delay_factor * .5) + + # Gather output + output += self._read_channel_timing(delay_factor=delay_factor, max_loops=max_loops) + + output = self._sanitize_output(output) + return output + + def set_base_prompt(self, *args, **kwargs): + """ + Extreme attaches an id to the prompt. The id increases with every command. + It needs to br stripped off to match the prompt. Eg. + + testhost.1 # + testhost.2 # + testhost.3 # + + If new config is loaded and not saved yet, a '* ' prefix appears before the + prompt, eg. + + * testhost.4 # + * testhost.5 # + """ + cur_base_prompt = super(ExtremeSSH, self).set_base_prompt(*args, **kwargs) + match = re.search(r'[\*\s]{0,2}(.*)\.\d+', cur_base_prompt) + if match: + # strip off .\d from base_prompt + self.base_prompt = match.group(1) + return self.base_prompt + + def config_mode(self, config_command=''): + """No configuration mode on Extreme.""" + return '' + + def check_config_mode(self, check_string='#'): + """Checks whether in configuration mode. Returns a boolean.""" + return super(ExtremeSSH, self).check_config_mode(check_string=check_string) + + def exit_config_mode(self, exit_config=''): + """No configuration mode on Extreme.""" + return '' From 5d9f9272c5381f14bc7d8f09d715f8c914e61e4b Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Sat, 1 Apr 2017 10:49:20 +0100 Subject: [PATCH 09/32] Issue #430: read_until_prompt_or_pattern does not apply timeout or sleep The read_until_prompt_or_pattern method in base_connection.BaseConnection reads data from the connection in a tight loop reading data from the device, waiting for the prompt or another pattern to be received. There are a couple of issues with this. 1. There is no way to break out of the loop - no timeout mechanism is applied, so we can get stuck here indefinitely if the device does not send the expected pattern or prompt. 2. There is no delay between successive calls to the read_channel method. This can lead to excessive CPU use as the process spins. It also has a negative impact when used in tandem with cooperative multitasking libraries such as eventlet (as used by OpenStack, in which the networking-generic-switch package uses netmiko and where I observed this issue). The _read_channel_expect method used by read_until_prompt and read_until_pattern avoids these issues by applying a timeout to the read and sleeping between each read. This change resolves the issue by combining the prompt and pattern into a combined regular expression and waiting for it to appear using _read_channel_expect. --- netmiko/base_connection.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index c73d4ab18..9423be9d4 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -320,18 +320,10 @@ def read_until_pattern(self, *args, **kwargs): def read_until_prompt_or_pattern(self, pattern='', re_flags=0): """Read until either self.base_prompt or pattern is detected. Return ALL data available.""" - output = '' - if not pattern: - pattern = re.escape(self.base_prompt) - base_prompt_pattern = re.escape(self.base_prompt) - while True: - try: - output += self.read_channel() - if re.search(pattern, output, flags=re_flags) or re.search(base_prompt_pattern, - output, flags=re_flags): - return output - except socket.timeout: - raise NetMikoTimeoutException("Timed-out reading channel, data not available.") + combined_pattern = re.escape(self.base_prompt) + if pattern: + combined_pattern += "|{}".format(pattern) + return self._read_channel_expect(combined_pattern, re_flags=re_flags) def telnet_login(self, pri_prompt_terminator='#', alt_prompt_terminator='>', username_pattern=r"sername", pwd_pattern=r"assword", From 6e4750c3c8917312c6dc504ff2d1dacca7a2cbfc Mon Sep 17 00:00:00 2001 From: liorf Date: Wed, 5 Apr 2017 14:39:26 +0300 Subject: [PATCH 10/32] Adding * to .Arista Networks EOS. in SNMP_MAPPER_BASE --- netmiko/snmp_autodetect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netmiko/snmp_autodetect.py b/netmiko/snmp_autodetect.py index 70924f235..4e212708a 100644 --- a/netmiko/snmp_autodetect.py +++ b/netmiko/snmp_autodetect.py @@ -33,7 +33,7 @@ # Higher priority indicates a better match. SNMP_MAPPER_BASE = { 'arista_eos': {"oid": ".1.3.6.1.2.1.1.1.0", - "expr": re.compile(r".Arista Networks EOS.", re.IGNORECASE), + "expr": re.compile(r".*Arista Networks EOS.*", re.IGNORECASE), "priority": 99}, 'hp_comware': {"oid": ".1.3.6.1.2.1.1.1.0", "expr": re.compile(r".*HP Comware.*", re.IGNORECASE), From aba023a9153c693bd598310cd49d4d517178a683 Mon Sep 17 00:00:00 2001 From: liorf Date: Wed, 5 Apr 2017 14:46:10 +0300 Subject: [PATCH 11/32] Adding * to .Arista Networks EOS. in SNMP_MAPPER_BASE --- netmiko/snmp_autodetect.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netmiko/snmp_autodetect.py b/netmiko/snmp_autodetect.py index 4e212708a..72924a3af 100644 --- a/netmiko/snmp_autodetect.py +++ b/netmiko/snmp_autodetect.py @@ -30,6 +30,7 @@ from netmiko.ssh_dispatcher import CLASS_MAPPER + # Higher priority indicates a better match. SNMP_MAPPER_BASE = { 'arista_eos': {"oid": ".1.3.6.1.2.1.1.1.0", From 37f6b375c2ddaa61bfd185097c0d71f8dab46835 Mon Sep 17 00:00:00 2001 From: Matej Vadnjal Date: Fri, 7 Apr 2017 15:52:26 +0200 Subject: [PATCH 12/32] Update SSH autodetect for Juniper EX series --- netmiko/ssh_autodetect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netmiko/ssh_autodetect.py b/netmiko/ssh_autodetect.py index c773c3794..17b2de94d 100644 --- a/netmiko/ssh_autodetect.py +++ b/netmiko/ssh_autodetect.py @@ -90,7 +90,7 @@ }, 'juniper_junos': { "cmd": "show version | match JUNOS", - "search_patterns": ["JUNOS Software Release"], + "search_patterns": ["JUNOS Software Release", "JUNOS .+ Software"], "priority": 99, "dispatch": "_autodetect_std", }, From 503df098f3b90a13c10546fb4569e5fc05e57038 Mon Sep 17 00:00:00 2001 From: Lindsay Hill Date: Fri, 7 Apr 2017 10:59:03 -0700 Subject: [PATCH 13/32] Tidied up README formatting Some of the Markdown wasn't rendering properly when viewed via Github. --- README.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 40f2b8358..20c19d2a4 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,17 @@ Multi-vendor library to simplify Paramiko SSH connections to network devices Python 2.7, 3.4, 3.5 -
#### Requires: + Paramiko >= 1.13+ scp >= 0.10.0 pyyaml pytest (for unit tests) - -
#### Supports: ###### Regularly tested + Arista vEOS Cisco ASA Cisco IOS @@ -33,6 +32,7 @@ Juniper Junos Linux ###### Limited testing + Avaya ERS Avaya VSP Brocade VDX @@ -48,6 +48,7 @@ Pluribus Vyatta VyOS ###### Experimental + A10 Alcatel-Lucent SR-OS Ciena SAOS @@ -57,17 +58,16 @@ Extreme F5 LTM Fortinet -
## Tutorials: ##### Standard Tutorial: + https://pynet.twb-tech.com/blog/automation/netmiko.html ##### SSH Proxy: -https://pynet.twb-tech.com/blog/automation/netmiko-proxy.html +https://pynet.twb-tech.com/blog/automation/netmiko-proxy.html -
## Examples: #### Create a dictionary representing the device. @@ -88,14 +88,14 @@ cisco_881 = { ``` -
#### Establish an SSH connection to the device by passing in the device dictionary. + ```py net_connect = ConnectHandler(**cisco_881) ``` -
#### Execute show commands. + ```py output = net_connect.send_command('show ip int brief') print(output) @@ -110,27 +110,27 @@ FastEthernet4 10.10.10.10 YES manual up up Vlan1 unassigned YES unset down down ``` -
#### For long-running commands, use `send_command_expect()` `send_command_expect` waits for the trailing prompt (or for an optional pattern) ```py net_connect.send_command_expect('write memory') ``` + ``` Building configuration... [OK] ``` -
#### Enter and exit enable mode. + ```py net_connect.enable() net_connect.exit_enable_mode() ``` -
#### Execute configuration change commands (will automatically enter into config mode) + ```py config_commands = [ 'logging buffered 20000', 'logging buffered 20010', @@ -157,7 +157,6 @@ If you have questions or would like to discuss Netmiko, a Netmiko channel exists -
--- Kirk Byers Python for Network Engineers From 8562aae956a4db9a887a35dc0c9a70ef4cbcb1e4 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Fri, 7 Apr 2017 14:19:15 -0700 Subject: [PATCH 14/32] Fixing minor pylama issue --- netmiko/base_connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index c73d4ab18..ad1d08b70 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -73,7 +73,7 @@ def __init__(self, ip='', host='', username='', password='', secret='', port=Non :type ssh_strict: bool :param system_host_keys: Load host keys from the user's 'known_hosts' file. :type system_host_keys: bool - :param alt_host_keys: If `True` host keys will be loaded from the file specified in + :param alt_host_keys: If `True` host keys will be loaded from the file specified in 'alt_key_file'. :type alt_host_keys: bool :param alt_key_file: SSH host key file to use (if alt_host_keys=True). From a4d5c0130ac0b29a656e9c52e31f16b64a318163 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Fri, 7 Apr 2017 14:50:51 -0700 Subject: [PATCH 15/32] Eliminate some code duplication --- netmiko/dell/dell_powerconnect_telnet.py | 56 +++++------------------- netmiko/ssh_dispatcher.py | 6 +-- 2 files changed, 13 insertions(+), 49 deletions(-) diff --git a/netmiko/dell/dell_powerconnect_telnet.py b/netmiko/dell/dell_powerconnect_telnet.py index 705f27076..c4167707a 100644 --- a/netmiko/dell/dell_powerconnect_telnet.py +++ b/netmiko/dell/dell_powerconnect_telnet.py @@ -1,18 +1,16 @@ """Dell Telnet Driver.""" from __future__ import unicode_literals -from netmiko.cisco_base_connection import CiscoSSHConnection -from netmiko.ssh_exception import NetMikoAuthenticationException import time -import re +from netmiko.cisco_base_connection import CiscoBaseConnection -class DellPowerConnectTelnet(CiscoSSHConnection): +class DellPowerConnectTelnet(CiscoBaseConnection): def disable_paging(self, command="terminal length 0", delay_factor=1): - print("MADE IT HERE:") """Must be in enable mode to disable paging.""" + debug = False + self.enable() - debug = True delay_factor = self.select_delay_factor(delay_factor) time.sleep(delay_factor * .1) self.clear_buffer() @@ -33,42 +31,10 @@ def telnet_login(self, pri_prompt_terminator='#', alt_prompt_terminator='>', username_pattern=r"User:", pwd_pattern=r"assword", delay_factor=1, max_loops=60): """Telnet login. Can be username/password or just password.""" - TELNET_RETURN = '\r\n' - - delay_factor = self.select_delay_factor(delay_factor) - time.sleep(1 * delay_factor) - - output = '' - return_msg = '' - i = 1 - while i <= max_loops: - try: - output = self.read_channel() - return_msg += output - - # Search for username pattern / send username - if re.search(username_pattern, output): - self.write_channel(self.username + TELNET_RETURN) - time.sleep(1 * delay_factor) - output = self.read_channel() - return_msg += output - - # Search for password pattern / send password - if re.search(pwd_pattern, output): - self.write_channel(self.password + TELNET_RETURN) - time.sleep(.5 * delay_factor) - output = self.read_channel() - return_msg += output - if pri_prompt_terminator in output or alt_prompt_terminator in output: - return return_msg - - # Check if proper data received - if pri_prompt_terminator in output or alt_prompt_terminator in output: - return return_msg - - self.write_channel(TELNET_RETURN) - time.sleep(.5 * delay_factor) - i += 1 - except EOFError: - msg = "Telnet login failed: {0}".format(self.host) - raise NetMikoAuthenticationException(msg) + super(DellPowerConnectTelnet, self).telnet_login( + pri_prompt_terminator=pri_prompt_terminator, + alt_prompt_terminator=alt_prompt_terminator, + username_pattern=username_pattern, + pwd_pattern=pwd_pattern, + delay_factor=delay_factor, + max_loops=max_loops) diff --git a/netmiko/ssh_dispatcher.py b/netmiko/ssh_dispatcher.py index 00aabf67c..6e4e13a45 100644 --- a/netmiko/ssh_dispatcher.py +++ b/netmiko/ssh_dispatcher.py @@ -75,7 +75,6 @@ 'fortinet': FortinetSSH, 'dell_force10': DellForce10SSH, 'dell_powerconnect': DellPowerConnectSSH, - 'dell_powerconnect_telnet': DellPowerConnectTelnet, 'paloalto_panos': PaloAltoPanosSSH, 'quanta_mesh': QuantaMeshSSH, 'aruba_os': ArubaSSH, @@ -97,12 +96,11 @@ # Add telnet drivers CLASS_MAPPER['cisco_ios_telnet'] = CiscoIosBase +CLASS_MAPPER['dell_powerconnect_telnet'] = DellPowerConnectTelnet CLASS_MAPPER['generic_termserver_telnet'] = TerminalServerTelnet -# Add general terminal_server driver +# Add general terminal_server driver and autodetect CLASS_MAPPER['terminal_server'] = TerminalServerSSH - -# Add autodetect driver (mapped to TerminalServerSSH) CLASS_MAPPER['autodetect'] = TerminalServerSSH platforms = list(CLASS_MAPPER.keys()) From a815a63b1d3e589f0383b98cebe07aeebb62b320 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sat, 8 Apr 2017 11:51:16 -0700 Subject: [PATCH 16/32] Fix Fortinet SSH key transport --- netmiko/base_connection.py | 6 ++++++ netmiko/fortinet/fortinet_ssh.py | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index b607624c9..e1de8fd99 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -118,6 +118,7 @@ def __init__(self, ip='', host='', username='', password='', secret='', port=Non # determine if telnet or SSH if '_telnet' in device_type: self.protocol = 'telnet' + self._modify_connection_params() self.establish_connection() self.session_preparation() else: @@ -139,6 +140,7 @@ def __init__(self, ip='', host='', username='', password='', secret='', port=Non # For SSH proxy support self.ssh_config_file = ssh_config_file + self._modify_connection_params() self.establish_connection() self.session_preparation() @@ -156,6 +158,10 @@ def __exit__(self, exc_type, exc_value, traceback): if exc_type is not None: raise exc_type(exc_value) + def _modify_connection_params(self): + """Modify connection parameters prior to SSH connection.""" + pass + def _timeout_exceeded(self, start, msg='Timeout exceeded!'): """ Raise NetMikoTimeoutException if waiting too much in the diff --git a/netmiko/fortinet/fortinet_ssh.py b/netmiko/fortinet/fortinet_ssh.py index 1f5840a2e..4cfa0084d 100644 --- a/netmiko/fortinet/fortinet_ssh.py +++ b/netmiko/fortinet/fortinet_ssh.py @@ -1,9 +1,17 @@ from __future__ import unicode_literals +import paramiko from netmiko.cisco_base_connection import CiscoSSHConnection class FortinetSSH(CiscoSSHConnection): + def _modify_connection_params(self): + """Modify connection parameters prior to SSH connection.""" + paramiko.Transport._preferred_kex = ('diffie-hellman-group14-sha1', + 'diffie-hellman-group-exchange-sha1', + 'diffie-hellman-group-exchange-sha256', + 'diffie-hellman-group1-sha1',) + def session_preparation(self): """Prepare the session after the connection has been established.""" self._test_channel_read() From 526b4c287e1ce49946006720065438dc4eb88011 Mon Sep 17 00:00:00 2001 From: Alexander Hofmann Date: Thu, 13 Apr 2017 13:03:54 +0200 Subject: [PATCH 17/32] Add Check Point Gaia support --- netmiko/checkpoint/__init__.py | 4 ++++ netmiko/checkpoint/checkpoint_gaia_ssh.py | 26 +++++++++++++++++++++++ netmiko/snmp_autodetect.py | 3 +++ netmiko/ssh_dispatcher.py | 2 ++ netmiko/utilities.py | 1 + setup.py | 1 + 6 files changed, 37 insertions(+) create mode 100644 netmiko/checkpoint/__init__.py create mode 100644 netmiko/checkpoint/checkpoint_gaia_ssh.py diff --git a/netmiko/checkpoint/__init__.py b/netmiko/checkpoint/__init__.py new file mode 100644 index 000000000..56967ca39 --- /dev/null +++ b/netmiko/checkpoint/__init__.py @@ -0,0 +1,4 @@ +from __future__ import unicode_literals +from netmiko.checkpoint.checkpoint_gaia_ssh import CheckPointGaiaSSH + +__all__ = ['CheckPointGaiaSSH'] diff --git a/netmiko/checkpoint/checkpoint_gaia_ssh.py b/netmiko/checkpoint/checkpoint_gaia_ssh.py new file mode 100644 index 000000000..591f3db09 --- /dev/null +++ b/netmiko/checkpoint/checkpoint_gaia_ssh.py @@ -0,0 +1,26 @@ +from __future__ import unicode_literals +from netmiko.base_connection import BaseConnection + + +class CheckPointGaiaSSH(BaseConnection): + """ + Implements methods for communicating with Check Point Gaia + firewalls. + """ + def session_preparation(self): + """ + Prepare the session after the connection has been established. + + Set the base prompt for interaction ('>'). + """ + self._test_channel_read() + self.set_base_prompt() + self.disable_paging(command="set clienv rows 0\n") + + def config_mode(self, config_command=''): + """No config mode for Check Point devices.""" + return '' + + def exit_config_mode(self, exit_config=''): + """No config mode for Check Point devices.""" + return '' diff --git a/netmiko/snmp_autodetect.py b/netmiko/snmp_autodetect.py index 70924f235..1e76bf882 100644 --- a/netmiko/snmp_autodetect.py +++ b/netmiko/snmp_autodetect.py @@ -59,6 +59,9 @@ 'fortinet': {"oid": ".1.3.6.1.2.1.1.1.0", "expr": re.compile(r"Forti.*", re.IGNORECASE), "priority": 80}, + 'checkpoint': {"oid": ".1.3.6.1.4.1.2620.1.6.16.9.0", + "expr": re.compile(r"CheckPoint"), + "priority": 79}, } # Ensure all SNMP device types are supported by Netmiko diff --git a/netmiko/ssh_dispatcher.py b/netmiko/ssh_dispatcher.py index 6b269ffca..3795f19bd 100644 --- a/netmiko/ssh_dispatcher.py +++ b/netmiko/ssh_dispatcher.py @@ -16,6 +16,7 @@ from netmiko.brocade import BrocadeNetironSSH from netmiko.brocade import BrocadeFastironSSH from netmiko.fortinet import FortinetSSH +from netmiko.checkpoint import CheckPointGaiaSSH from netmiko.a10 import A10SSH from netmiko.avaya import AvayaVspSSH from netmiko.avaya import AvayaErsSSH @@ -72,6 +73,7 @@ 'extreme': ExtremeSSH, 'alcatel_sros': AlcatelSrosSSH, 'fortinet': FortinetSSH, + 'checkpoint': CheckPointGaiaSSH, 'dell_force10': DellForce10SSH, 'dell_powerconnect': DellPowerConnectSSH, 'paloalto_panos': PaloAltoPanosSSH, diff --git a/netmiko/utilities.py b/netmiko/utilities.py index a66adf6e9..6d25d3ddf 100644 --- a/netmiko/utilities.py +++ b/netmiko/utilities.py @@ -14,6 +14,7 @@ 'hp_comware': 'display current-configuration', 'huawei': 'display current-configuration', 'fortinet': 'show full-configuration', + 'checkpoint': 'show configuration', 'cisco_wlc': 'show run-config', 'enterasys': 'show running-config', 'dell_force10': 'show running-config', diff --git a/setup.py b/setup.py index 8b6517895..1129a522d 100644 --- a/setup.py +++ b/setup.py @@ -53,6 +53,7 @@ def find_version(*file_paths): 'netmiko/extreme', 'netmiko/f5', 'netmiko/fortinet', + 'netmiko/checkpoint', 'netmiko/hp', 'netmiko/huawei', 'netmiko/juniper', From 5d992eb946925add939d1ac5147bcf8cc8c8d8b1 Mon Sep 17 00:00:00 2001 From: Alexander Hofmann Date: Thu, 13 Apr 2017 13:33:16 +0200 Subject: [PATCH 18/32] Check Point support: fixed pep8 errors --- netmiko/base_connection.py | 2 +- netmiko/snmp_autodetect.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index c73d4ab18..ad1d08b70 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -73,7 +73,7 @@ def __init__(self, ip='', host='', username='', password='', secret='', port=Non :type ssh_strict: bool :param system_host_keys: Load host keys from the user's 'known_hosts' file. :type system_host_keys: bool - :param alt_host_keys: If `True` host keys will be loaded from the file specified in + :param alt_host_keys: If `True` host keys will be loaded from the file specified in 'alt_key_file'. :type alt_host_keys: bool :param alt_key_file: SSH host key file to use (if alt_host_keys=True). diff --git a/netmiko/snmp_autodetect.py b/netmiko/snmp_autodetect.py index 1e76bf882..83bfbf371 100644 --- a/netmiko/snmp_autodetect.py +++ b/netmiko/snmp_autodetect.py @@ -60,8 +60,8 @@ "expr": re.compile(r"Forti.*", re.IGNORECASE), "priority": 80}, 'checkpoint': {"oid": ".1.3.6.1.4.1.2620.1.6.16.9.0", - "expr": re.compile(r"CheckPoint"), - "priority": 79}, + "expr": re.compile(r"CheckPoint"), + "priority": 79}, } # Ensure all SNMP device types are supported by Netmiko From 796a644b3536cf066728cff2fb5fef796de817fd Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Thu, 20 Apr 2017 16:20:19 -0700 Subject: [PATCH 19/32] Change to checkpoint reference in dispatcher --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 20c19d2a4..c23793e7d 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ A10 Alcatel-Lucent SR-OS Ciena SAOS Cisco Telepresence +CheckPoint Gaia Enterasys Extreme F5 LTM From b4df9cca05a9ebfa444742cc0638da5e8d664a32 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Thu, 20 Apr 2017 21:48:23 -0700 Subject: [PATCH 20/32] Add support for send_config_set where command is a string --- netmiko/__init__.py | 2 +- netmiko/base_connection.py | 4 ++++ netmiko/cisco/cisco_wlc_ssh.py | 4 ++++ netmiko/py23_compat.py | 15 +++++++++++++++ netmiko/ssh_dispatcher.py | 2 +- tests/test_netmiko_config.py | 15 ++++----------- 6 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 netmiko/py23_compat.py diff --git a/netmiko/__init__.py b/netmiko/__init__.py index 25ed8b62c..4b7654156 100644 --- a/netmiko/__init__.py +++ b/netmiko/__init__.py @@ -21,7 +21,7 @@ NetmikoTimeoutError = NetMikoTimeoutException NetmikoAuthError = NetMikoAuthenticationException -__version__ = '1.3.1' +__version__ = '1.3.2' __all__ = ('ConnectHandler', 'ssh_dispatcher', 'platforms', 'SCPConn', 'FileTransfer', 'NetMikoTimeoutException', 'NetMikoAuthenticationException', diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index e1de8fd99..2f9f743cf 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -22,6 +22,7 @@ from netmiko.netmiko_globals import MAX_BUFFER, BACKSPACE_CHAR from netmiko.ssh_exception import NetMikoTimeoutException, NetMikoAuthenticationException from netmiko.utilities import write_bytes +from netmiko.py23_compat import string_types from netmiko import log @@ -937,6 +938,9 @@ def send_config_set(self, config_commands=None, exit_config_mode=True, delay_fac delay_factor = self.select_delay_factor(delay_factor) if config_commands is None: return '' + elif isinstance(config_commands, string_types): + config_commands = (config_commands,) + if not hasattr(config_commands, '__iter__'): raise ValueError("Invalid argument passed into send_config_set") diff --git a/netmiko/cisco/cisco_wlc_ssh.py b/netmiko/cisco/cisco_wlc_ssh.py index e9990d52b..2a357b270 100644 --- a/netmiko/cisco/cisco_wlc_ssh.py +++ b/netmiko/cisco/cisco_wlc_ssh.py @@ -4,6 +4,7 @@ import time from netmiko.base_connection import BaseConnection +from netmiko.py23_compat import string_types class CiscoWlcSSH(BaseConnection): @@ -137,6 +138,9 @@ def send_config_set(self, config_commands=None, exit_config_mode=True, delay_fac delay_factor = self.select_delay_factor(delay_factor) if config_commands is None: return '' + elif isinstance(config_commands, string_types): + config_commands = (config_commands,) + if not hasattr(config_commands, '__iter__'): raise ValueError("Invalid argument passed into send_config_set") diff --git a/netmiko/py23_compat.py b/netmiko/py23_compat.py new file mode 100644 index 000000000..fb753e176 --- /dev/null +++ b/netmiko/py23_compat.py @@ -0,0 +1,15 @@ +"""Simplify Python3 compatibility. Modeled after six behavior for small set of things""" +from __future__ import print_function +from __future__ import unicode_literals + +import sys + +PY2 = sys.version_info.major == 2 +PY3 = sys.version_info.major == 3 + +if sys.version_info.major == 3: + string_types = (str,) + text_type = str +else: + string_types = (basestring,) # noqa + text_type = unicode # noqa diff --git a/netmiko/ssh_dispatcher.py b/netmiko/ssh_dispatcher.py index c9aa680b6..68bf39101 100644 --- a/netmiko/ssh_dispatcher.py +++ b/netmiko/ssh_dispatcher.py @@ -74,7 +74,7 @@ 'extreme': ExtremeSSH, 'alcatel_sros': AlcatelSrosSSH, 'fortinet': FortinetSSH, - 'checkpoint': CheckPointGaiaSSH, + 'checkpoint_gaia': CheckPointGaiaSSH, 'dell_force10': DellForce10SSH, 'dell_powerconnect': DellPowerConnectSSH, 'paloalto_panos': PaloAltoPanosSSH, diff --git a/tests/test_netmiko_config.py b/tests/test_netmiko_config.py index f8bf6f368..275e3e96d 100755 --- a/tests/test_netmiko_config.py +++ b/tests/test_netmiko_config.py @@ -56,22 +56,18 @@ def test_exit_config_mode(net_connect, commands, expected_responses): assert net_connect.check_config_mode() == False def test_command_set(net_connect, commands, expected_responses): - ''' - Test sending configuration commands - ''' - print(1) + """Test sending configuration commands.""" config_commands = commands['config'] support_commit = commands.get('support_commit') config_verify = commands['config_verification'] - print(2) - net_connect.send_config_set(config_commands[0:1]) + # Set to initial value and testing sending command as a string + net_connect.send_config_set(config_commands[0]) if support_commit: net_connect.commit() - print(3) cmd_response = expected_responses.get('cmd_response_init') - config_commands_output = net_connect.send_command_expect(config_verify) + config_commands_output = net_connect.send_command(config_verify) print(config_verify) print(config_commands_output) if cmd_response: @@ -79,15 +75,12 @@ def test_command_set(net_connect, commands, expected_responses): else: assert config_commands[0] in config_commands_output - print(4) net_connect.send_config_set(config_commands) if support_commit: net_connect.commit() - print(5) cmd_response = expected_responses.get('cmd_response_final') config_commands_output = net_connect.send_command_expect(config_verify) - print(6) if cmd_response: assert cmd_response in config_commands_output else: From 7c613d4c8f6efe8193cf3df153903e07761ba8b8 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Fri, 21 Apr 2017 10:49:57 -0700 Subject: [PATCH 21/32] Improvements to Extreme drivers --- netmiko/extreme/extreme_ssh.py | 98 +++++++--------------------------- 1 file changed, 19 insertions(+), 79 deletions(-) diff --git a/netmiko/extreme/extreme_ssh.py b/netmiko/extreme/extreme_ssh.py index 2e6373234..631bd054b 100644 --- a/netmiko/extreme/extreme_ssh.py +++ b/netmiko/extreme/extreme_ssh.py @@ -1,93 +1,22 @@ """Extreme support.""" from __future__ import unicode_literals + import re -import time from netmiko.cisco_base_connection import CiscoSSHConnection -QUESTIONS = { - 'save': 'Do you want to save configuration to', -} - - class ExtremeSSH(CiscoSSHConnection): - """Extreme support.""" + """Extreme support. + Designed for EXOS >= 15.0 + """ def session_preparation(self): - """ - Extreme requires enable mode to disable paging. - """ + """Extreme requires enable mode to disable paging.""" self._test_channel_read() + self.enable() self.set_base_prompt() self.disable_paging(command="disable clipaging\n") - def answer_questions(self, *args, **kwargs): - """ - Send questions/answers with send_command_timing. - """ - if len(args) >= 1: - command_string = args[0] - - # handle commands which return a question - output = self.send_command_timing(command_string) - if QUESTIONS[command_string] in output: - output += self.send_command_timing('yes') - - return output - - def send_command(self, *args, **kwargs): - """ - Catch questions from the CLI. - Handle prompt changes during command executions. - """ - if len(args) >= 1: - command_string = args[0] - - # command that prompt a question - if command_string in QUESTIONS.keys(): - output = self.answer_questions(command_string) - - # all other commands - else: - # Turn off 'auto_find_prompt' which loses the striped base prompt - kwargs.update({'auto_find_prompt': False}) - # refresh prompt, which could have been changed on previous command - self.set_base_prompt() - output = super(ExtremeSSH, self).send_command(*args, **kwargs) - - return output - - def send_config_set(self, config_commands=None, exit_config_mode=True, delay_factor=1, - max_loops=150, strip_prompt=False, strip_command=False): - """ - Overwrite send_config_set to acomodate for commands which return a question - and expect a reply. - """ - delay_factor = self.select_delay_factor(delay_factor) - if config_commands is None: - return '' - if not hasattr(config_commands, '__iter__'): - raise ValueError("Invalid argument passed into send_config_set") - - # Send config commands - output = '' - for command_string in config_commands: - - # command that prompt a question - if command_string in QUESTIONS.keys(): - output += self.answer_questions(command_string) - - # all other commands - else: - self.write_channel(self.normalize_cmd(command_string)) - time.sleep(delay_factor * .5) - - # Gather output - output += self._read_channel_timing(delay_factor=delay_factor, max_loops=max_loops) - - output = self._sanitize_output(output) - return output - def set_base_prompt(self, *args, **kwargs): """ Extreme attaches an id to the prompt. The id increases with every command. @@ -104,11 +33,22 @@ def set_base_prompt(self, *args, **kwargs): * testhost.5 # """ cur_base_prompt = super(ExtremeSSH, self).set_base_prompt(*args, **kwargs) - match = re.search(r'[\*\s]{0,2}(.*)\.\d+', cur_base_prompt) + # Strip off any leading * or whitespace chars; strip off trailing period and digits + match = re.search(r'[\*\s]*(.*)\.\d+', cur_base_prompt) if match: - # strip off .\d from base_prompt self.base_prompt = match.group(1) return self.base_prompt + else: + return self.base_prompt + + def send_command(self, *args, **kwargs): + """Extreme needs special handler here due to the prompt changes.""" + + # Change send_command behavior to use self.base_prompt + kwargs.setdefault({'auto_find_prompt': False}) + # refresh self.base_prompt + self.set_base_prompt() + return super(ExtremeSSH, self).send_command(*args, **kwargs) def config_mode(self, config_command=''): """No configuration mode on Extreme.""" From b6fc0be5f2562b87023a888deabc31baea76cf60 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sat, 22 Apr 2017 09:10:50 -0700 Subject: [PATCH 22/32] Fixes #82, add ASA login support --- netmiko/__init__.py | 2 +- netmiko/cisco/cisco_asa_ssh.py | 32 +++++++++++++++++++++++++++++++- tests/test_asa.sh | 2 ++ tests/test_suite_alt.sh | 2 ++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/netmiko/__init__.py b/netmiko/__init__.py index 4b7654156..1b7da4457 100644 --- a/netmiko/__init__.py +++ b/netmiko/__init__.py @@ -21,7 +21,7 @@ NetmikoTimeoutError = NetMikoTimeoutException NetmikoAuthError = NetMikoAuthenticationException -__version__ = '1.3.2' +__version__ = '1.3.3' __all__ = ('ConnectHandler', 'ssh_dispatcher', 'platforms', 'SCPConn', 'FileTransfer', 'NetMikoTimeoutException', 'NetMikoAuthenticationException', diff --git a/netmiko/cisco/cisco_asa_ssh.py b/netmiko/cisco/cisco_asa_ssh.py index a34977006..71e22e085 100644 --- a/netmiko/cisco/cisco_asa_ssh.py +++ b/netmiko/cisco/cisco_asa_ssh.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import re +import time from netmiko.cisco_base_connection import CiscoSSHConnection @@ -11,7 +12,10 @@ def session_preparation(self): """Prepare the session after the connection has been established.""" self._test_channel_read() self.set_base_prompt() - self.enable() + if self.secret: + self.enable() + else: + self.asa_login() self.disable_paging(command="terminal pager 0\n") self.set_terminal_width(command="terminal width 511\n") @@ -69,3 +73,29 @@ def set_base_prompt(self, *args, **kwargs): # strip off (conf.* from base_prompt self.base_prompt = match.group(1) return self.base_prompt + + def asa_login(self): + """ + Handle ASA reaching privilege level 15 using login + + twb-dc-fw1> login + Username: admin + Password: ************ + """ + delay_factor = self.select_delay_factor(0) + + i = 1 + max_attempts = 50 + self.write_channel("login\n") + while i <= max_attempts: + time.sleep(.5 * delay_factor) + output = self.read_channel() + if 'sername' in output: + self.write_channel(self.username + '\n') + elif 'ssword' in output: + self.write_channel(self.password + '\n') + elif '#' in output: + break + else: + self.write_channel("login\n") + i += 1 diff --git a/tests/test_asa.sh b/tests/test_asa.sh index 80b371ce0..0b7b90a0c 100755 --- a/tests/test_asa.sh +++ b/tests/test_asa.sh @@ -4,6 +4,8 @@ RETURN_CODE=0 # Exit on the first test failure and set RETURN_CODE = 1 echo "Starting tests...good luck:" \ +&& py.test -v test_netmiko_show.py --test_device cisco_asa_login \ +&& py.test -v test_netmiko_config.py --test_device cisco_asa_login \ && py.test -v test_netmiko_show.py --test_device cisco_asa \ && py.test -v test_netmiko_config.py --test_device cisco_asa \ || RETURN_CODE=1 diff --git a/tests/test_suite_alt.sh b/tests/test_suite_alt.sh index 2c09d705e..513a99c79 100755 --- a/tests/test_suite_alt.sh +++ b/tests/test_suite_alt.sh @@ -41,6 +41,8 @@ echo "Starting tests...good luck:" \ && echo "Cisco ASA" \ && py.test -v test_netmiko_show.py --test_device cisco_asa \ && py.test -v test_netmiko_config.py --test_device cisco_asa \ +&& py.test -v test_netmiko_show.py --test_device cisco_asa_login \ +&& py.test -v test_netmiko_config.py --test_device cisco_asa_login \ \ && echo "Cisco IOS-XR" \ && py.test -v test_netmiko_show.py --test_device cisco_xrv \ From 081a9e84268865ddef714e1b1d4d400c9f6a88a2 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sat, 22 Apr 2017 09:42:08 -0700 Subject: [PATCH 23/32] Fixes #146; SSH key auth tests using Cisco --- tests/test_cisco.sh | 13 +++++++++++++ tests/test_suite_alt.sh | 6 ++++++ 2 files changed, 19 insertions(+) create mode 100755 tests/test_cisco.sh diff --git a/tests/test_cisco.sh b/tests/test_cisco.sh new file mode 100755 index 000000000..5f2439ec8 --- /dev/null +++ b/tests/test_cisco.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +RETURN_CODE=0 + +# Exit on the first test failure and set RETURN_CODE = 1 +echo "Cisco IOS SSH (including SCP) using key auth" \ +&& py.test -v test_netmiko_scp.py --test_device cisco881_key \ +&& py.test -v test_netmiko_tcl.py --test_device cisco881_key \ +&& py.test -v test_netmiko_show.py --test_device cisco881_key \ +&& py.test -v test_netmiko_config.py --test_device cisco881_key \ +|| RETURN_CODE=1 + +exit $RETURN_CODE diff --git a/tests/test_suite_alt.sh b/tests/test_suite_alt.sh index 513a99c79..360303716 100755 --- a/tests/test_suite_alt.sh +++ b/tests/test_suite_alt.sh @@ -7,6 +7,12 @@ echo "Starting tests...good luck:" \ && echo "Linux SSH (using keys)" \ && py.test -s -v test_netmiko_show.py --test_device linux_srv1 \ \ +&& echo "Cisco IOS SSH (including SCP) using key auth" \ +&& py.test -v test_netmiko_scp.py --test_device cisco881_key \ +&& py.test -v test_netmiko_tcl.py --test_device cisco881_key \ +&& py.test -v test_netmiko_show.py --test_device cisco881_key \ +&& py.test -v test_netmiko_config.py --test_device cisco881_key \ +\ && echo "Cisco IOS SSH (including SCP)" \ && py.test -v test_netmiko_scp.py --test_device cisco881 \ && py.test -v test_netmiko_tcl.py --test_device cisco881 \ From 24d39ce28484af0b4465f53c55e1ed0342f92c30 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sat, 22 Apr 2017 16:27:49 -0700 Subject: [PATCH 24/32] Making SSH config operate in proper way with respect host specifier --- netmiko/base_connection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index 2f9f743cf..f177edfd6 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -416,8 +416,8 @@ def _use_ssh_config(self, dict_arg): ssh_config_instance = paramiko.SSHConfig() with io.open(full_path, "rt", encoding='utf-8') as f: ssh_config_instance.parse(f) - host_specifier = "{0}:{1}".format(self.host, self.port) - source = ssh_config_instance.lookup(host_specifier) + # host_specifier = "{0}:{1}".format(self.host, self.port) + source = ssh_config_instance.lookup(self.host) else: source = {} From 1791d299c390fca6e6cd2c5e9baff89ba99b107b Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sat, 22 Apr 2017 16:28:20 -0700 Subject: [PATCH 25/32] Elminate old host specifier --- netmiko/base_connection.py | 1 - 1 file changed, 1 deletion(-) diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index f177edfd6..87e137502 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -416,7 +416,6 @@ def _use_ssh_config(self, dict_arg): ssh_config_instance = paramiko.SSHConfig() with io.open(full_path, "rt", encoding='utf-8') as f: ssh_config_instance.parse(f) - # host_specifier = "{0}:{1}".format(self.host, self.port) source = ssh_config_instance.lookup(self.host) else: source = {} From 32eb02bbed7c28a255c59a9e46deb99223b76424 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sat, 22 Apr 2017 16:36:23 -0700 Subject: [PATCH 26/32] Add tests that use SSH config --- tests/test_cisco.sh | 9 ++++----- tests/test_suite_alt.sh | 4 ++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/test_cisco.sh b/tests/test_cisco.sh index 5f2439ec8..cb4e5f341 100755 --- a/tests/test_cisco.sh +++ b/tests/test_cisco.sh @@ -3,11 +3,10 @@ RETURN_CODE=0 # Exit on the first test failure and set RETURN_CODE = 1 -echo "Cisco IOS SSH (including SCP) using key auth" \ -&& py.test -v test_netmiko_scp.py --test_device cisco881_key \ -&& py.test -v test_netmiko_tcl.py --test_device cisco881_key \ -&& py.test -v test_netmiko_show.py --test_device cisco881_key \ -&& py.test -v test_netmiko_config.py --test_device cisco881_key \ +echo "Cisco IOS SSH" \ +&& py.test -v test_netmiko_show.py --test_device cisco881_ssh_config \ +&& py.test -v test_netmiko_config.py --test_device cisco881_ssh_config \ || RETURN_CODE=1 exit $RETURN_CODE + diff --git a/tests/test_suite_alt.sh b/tests/test_suite_alt.sh index 360303716..9248f01a1 100755 --- a/tests/test_suite_alt.sh +++ b/tests/test_suite_alt.sh @@ -19,6 +19,10 @@ echo "Starting tests...good luck:" \ && py.test -v test_netmiko_show.py --test_device cisco881 \ && py.test -v test_netmiko_config.py --test_device cisco881 \ \ +echo "Cisco IOS using SSH config with SSH Proxy" \ +&& py.test -v test_netmiko_show.py --test_device cisco881_ssh_config \ +&& py.test -v test_netmiko_config.py --test_device cisco881_ssh_config \ +\ && echo "Cisco IOS telnet" \ && py.test -v test_netmiko_show.py --test_device cisco881_telnet \ && py.test -v test_netmiko_config.py --test_device cisco881_telnet \ From eadfdf54284c5823fe7b8a148d0798dede1f313b Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Mon, 24 Apr 2017 14:45:17 -0700 Subject: [PATCH 27/32] Fix missing re.escape on certain regular expression patterns --- netmiko/cisco_base_connection.py | 6 +++--- netmiko/vyos/vyos_ssh.py | 4 ++-- tests/test_suite_alt.sh | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/netmiko/cisco_base_connection.py b/netmiko/cisco_base_connection.py index ff229d5bc..4614287a2 100644 --- a/netmiko/cisco_base_connection.py +++ b/netmiko/cisco_base_connection.py @@ -27,7 +27,7 @@ def check_config_mode(self, check_string=')#', pattern=''): Cisco IOS devices abbreviate the prompt at 20 chars in config mode """ if not pattern: - pattern = self.base_prompt[:16] + pattern = re.escape(self.base_prompt[:16]) return super(CiscoBaseConnection, self).check_config_mode(check_string=check_string, pattern=pattern) @@ -38,14 +38,14 @@ def config_mode(self, config_command='config term', pattern=''): Cisco IOS devices abbreviate the prompt at 20 chars in config mode """ if not pattern: - pattern = self.base_prompt[:16] + pattern = re.escape(self.base_prompt[:16]) return super(CiscoBaseConnection, self).config_mode(config_command=config_command, pattern=pattern) def exit_config_mode(self, exit_config='end', pattern=''): """Exit from configuration mode.""" if not pattern: - pattern = self.base_prompt[:16] + pattern = re.escape(self.base_prompt[:16]) return super(CiscoBaseConnection, self).exit_config_mode(exit_config=exit_config, pattern=pattern) diff --git a/netmiko/vyos/vyos_ssh.py b/netmiko/vyos/vyos_ssh.py index 98faf4283..6a6bd50cb 100644 --- a/netmiko/vyos/vyos_ssh.py +++ b/netmiko/vyos/vyos_ssh.py @@ -28,11 +28,11 @@ def check_config_mode(self, check_string='#'): """Checks if the device is in configuration mode""" return super(VyOSSSH, self).check_config_mode(check_string=check_string) - def config_mode(self, config_command='configure', pattern='[edit]'): + def config_mode(self, config_command='configure', pattern=r'[edit]'): """Enter configuration mode.""" return super(VyOSSSH, self).config_mode(config_command=config_command, pattern=pattern) - def exit_config_mode(self, exit_config='exit', pattern='exit'): + def exit_config_mode(self, exit_config='exit', pattern=r'exit'): """Exit configuration mode""" output = "" if self.check_config_mode(): diff --git a/tests/test_suite_alt.sh b/tests/test_suite_alt.sh index 9248f01a1..5ab0bb7b1 100755 --- a/tests/test_suite_alt.sh +++ b/tests/test_suite_alt.sh @@ -19,7 +19,7 @@ echo "Starting tests...good luck:" \ && py.test -v test_netmiko_show.py --test_device cisco881 \ && py.test -v test_netmiko_config.py --test_device cisco881 \ \ -echo "Cisco IOS using SSH config with SSH Proxy" \ +&& echo "Cisco IOS using SSH config with SSH Proxy" \ && py.test -v test_netmiko_show.py --test_device cisco881_ssh_config \ && py.test -v test_netmiko_config.py --test_device cisco881_ssh_config \ \ From 3224aad45e5b93486185123f26ccaec27ce71b7b Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Mon, 1 May 2017 09:44:11 -0700 Subject: [PATCH 28/32] Fix bug with inline transfer not reading all of the TCLSH session data --- netmiko/scp_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netmiko/scp_handler.py b/netmiko/scp_handler.py index 21743cc30..322db063e 100644 --- a/netmiko/scp_handler.py +++ b/netmiko/scp_handler.py @@ -381,7 +381,8 @@ def put_file(self): self.ssh_ctl_chan.write_channel(TCL_EXIT + "\r") time.sleep(1) - output += self.ssh_ctl_chan.read_channel() + # Read all data remaining from the TCLSH session + output += self.ssh_ctl_chan._read_channel_expect() return output def get_file(self): From 917ac4a8759613607e9b630385b13abe70dd4846 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Mon, 1 May 2017 12:14:18 -0700 Subject: [PATCH 29/32] Improving issue with InLine Transfer and excessive delay on replace operation --- netmiko/base_connection.py | 7 ++++--- netmiko/scp_handler.py | 26 ++++++++++++++++---------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index 87e137502..67614a052 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -240,7 +240,7 @@ def read_channel(self): self._unlock_netmiko_session() return output - def _read_channel_expect(self, pattern='', re_flags=0): + def _read_channel_expect(self, pattern='', re_flags=0, max_loops=None): """ Function that reads channel until pattern is detected. @@ -261,10 +261,11 @@ def _read_channel_expect(self, pattern='', re_flags=0): if debug: print("Pattern is: {}".format(pattern)) - # Will loop for self.timeout time (unless modified by global_delay_factor) + # Will loop for self.timeout time (override with max_loops argument) i = 1 loop_delay = .1 - max_loops = self.timeout / loop_delay + if not max_loops: + max_loops = self.timeout / loop_delay while i < max_loops: if self.protocol == 'ssh': try: diff --git a/netmiko/scp_handler.py b/netmiko/scp_handler.py index 322db063e..5ff70eb3f 100644 --- a/netmiko/scp_handler.py +++ b/netmiko/scp_handler.py @@ -312,8 +312,8 @@ def __exit__(self, exc_type, exc_value, traceback): def _enter_tcl_mode(self): TCL_ENTER = 'tclsh' cmd_failed = ['Translating "tclsh"', '% Unknown command', '% Bad IP address'] - output = self.ssh_ctl_chan.send_command_timing(TCL_ENTER, strip_prompt=False, - strip_command=False) + output = self.ssh_ctl_chan.send_command(TCL_ENTER, expect_string=r"\(tcl\)#", + strip_prompt=False, strip_command=False) for pattern in cmd_failed: if pattern in output: raise ValueError("Failed to enter tclsh mode on router: {}".format(output)) @@ -364,17 +364,23 @@ def put_file(self): file_contents = self.source_config file_contents = self._tcl_newline_rationalize(file_contents) + # Try to remove any existing data + self.ssh_ctl_chan.clear_buffer() + self.ssh_ctl_chan.write_channel(TCL_FILECMD_ENTER) + time.sleep(.25) self.ssh_ctl_chan.write_channel(file_contents) self.ssh_ctl_chan.write_channel(TCL_FILECMD_EXIT + "\r") + time.sleep(2) - output = '' - while True: - time.sleep(1.5) - new_output = self.ssh_ctl_chan.read_channel() - if not new_output: - break - output += new_output + # This operation can be slow (depending on the size of the file) + # File paste and TCL_FILECMD_exit should be indicated by "router(tcl)#" + max_loops = 400 + if self.file_size >= 2500: + max_loops = 800 + elif self.file_size >= 7500: + max_loops = 1500 + output = self.ssh_ctl_chan._read_channel_expect(pattern=r"\(tcl\)", max_loops=max_loops) # The file doesn't write until tclquit TCL_EXIT = 'tclquit' @@ -382,7 +388,7 @@ def put_file(self): time.sleep(1) # Read all data remaining from the TCLSH session - output += self.ssh_ctl_chan._read_channel_expect() + output += self.ssh_ctl_chan._read_channel_expect(max_loops=max_loops) return output def get_file(self): From c77a5272c6c2da14b94175562f3932615d55cea4 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Mon, 1 May 2017 13:20:52 -0700 Subject: [PATCH 30/32] Improving reliability of inline file transfer for larger files --- netmiko/scp_handler.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/netmiko/scp_handler.py b/netmiko/scp_handler.py index 5ff70eb3f..6a2649812 100644 --- a/netmiko/scp_handler.py +++ b/netmiko/scp_handler.py @@ -371,15 +371,21 @@ def put_file(self): time.sleep(.25) self.ssh_ctl_chan.write_channel(file_contents) self.ssh_ctl_chan.write_channel(TCL_FILECMD_EXIT + "\r") - time.sleep(2) - # This operation can be slow (depending on the size of the file) - # File paste and TCL_FILECMD_exit should be indicated by "router(tcl)#" + # This operation can be slow (depends on the size of the file) max_loops = 400 + sleep_time = 4 if self.file_size >= 2500: - max_loops = 800 - elif self.file_size >= 7500: max_loops = 1500 + sleep_time = 12 + elif self.file_size >= 7500: + max_loops = 3000 + sleep_time = 25 + + # Initial delay + time.sleep(sleep_time) + + # File paste and TCL_FILECMD_exit should be indicated by "router(tcl)#" output = self.ssh_ctl_chan._read_channel_expect(pattern=r"\(tcl\)", max_loops=max_loops) # The file doesn't write until tclquit From c8ac7a7dea39381ab483d39fb3b92f17ea282ce0 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Mon, 1 May 2017 16:45:17 -0700 Subject: [PATCH 31/32] Release 1.4.0 --- netmiko/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netmiko/__init__.py b/netmiko/__init__.py index 1b7da4457..033ea7235 100644 --- a/netmiko/__init__.py +++ b/netmiko/__init__.py @@ -21,7 +21,7 @@ NetmikoTimeoutError = NetMikoTimeoutException NetmikoAuthError = NetMikoAuthenticationException -__version__ = '1.3.3' +__version__ = '1.4.0' __all__ = ('ConnectHandler', 'ssh_dispatcher', 'platforms', 'SCPConn', 'FileTransfer', 'NetMikoTimeoutException', 'NetMikoAuthenticationException', From 6aa565bcab503af880b839ea041b5e5cf07025e1 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Wed, 3 May 2017 10:01:17 -0700 Subject: [PATCH 32/32] Fixes #456 extreme issue with send_command --- netmiko/extreme/extreme_ssh.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netmiko/extreme/extreme_ssh.py b/netmiko/extreme/extreme_ssh.py index 631bd054b..2b4c64ebe 100644 --- a/netmiko/extreme/extreme_ssh.py +++ b/netmiko/extreme/extreme_ssh.py @@ -45,7 +45,8 @@ def send_command(self, *args, **kwargs): """Extreme needs special handler here due to the prompt changes.""" # Change send_command behavior to use self.base_prompt - kwargs.setdefault({'auto_find_prompt': False}) + kwargs.setdefault('auto_find_prompt', False) + # refresh self.base_prompt self.set_base_prompt() return super(ExtremeSSH, self).send_command(*args, **kwargs)