From 6d52edc68fbda71bc3e2e0659a6e02acf9afa28b Mon Sep 17 00:00:00 2001 From: Tris Emmy Wilson Date: Thu, 12 Sep 2019 18:17:19 -0500 Subject: [PATCH 01/39] add UnifiSwitchSSH driver for Ubiquiti UniFi switches --- netmiko/ssh_dispatcher.py | 2 ++ netmiko/ubiquiti/__init__.py | 3 ++- netmiko/ubiquiti/unifi_switch_ssh.py | 27 +++++++++++++++++++++++++++ tests/etc/commands.yml.example | 10 ++++++++++ tests/etc/responses.yml.example | 10 ++++++++++ tests/test_ubiquiti_unifi.sh | 12 ++++++++++++ 6 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 netmiko/ubiquiti/unifi_switch_ssh.py create mode 100755 tests/test_ubiquiti_unifi.sh diff --git a/netmiko/ssh_dispatcher.py b/netmiko/ssh_dispatcher.py index 17981bb33..0feebbc7e 100644 --- a/netmiko/ssh_dispatcher.py +++ b/netmiko/ssh_dispatcher.py @@ -74,6 +74,7 @@ from netmiko.terminal_server import TerminalServerSSH from netmiko.terminal_server import TerminalServerTelnet from netmiko.ubiquiti import UbiquitiEdgeSSH +from netmiko.ubiquiti import UnifiSwitchSSH from netmiko.vyos import VyOSSSH @@ -158,6 +159,7 @@ "ruckus_fastiron": RuckusFastironSSH, "ubiquiti_edge": UbiquitiEdgeSSH, "ubiquiti_edgeswitch": UbiquitiEdgeSSH, + "ubiquiti_unifi": UnifiSwitchSSH, "vyatta_vyos": VyOSSSH, "vyos": VyOSSSH, } diff --git a/netmiko/ubiquiti/__init__.py b/netmiko/ubiquiti/__init__.py index 066697941..5b17570df 100644 --- a/netmiko/ubiquiti/__init__.py +++ b/netmiko/ubiquiti/__init__.py @@ -1,3 +1,4 @@ from netmiko.ubiquiti.edge_ssh import UbiquitiEdgeSSH +from netmiko.ubiquiti.unifi_switch_ssh import UnifiSwitchSSH -__all__ = ["UbiquitiEdgeSSH"] +__all__ = ["UbiquitiEdgeSSH", "UnifiSwitchSSH"] diff --git a/netmiko/ubiquiti/unifi_switch_ssh.py b/netmiko/ubiquiti/unifi_switch_ssh.py new file mode 100644 index 000000000..5a585b8db --- /dev/null +++ b/netmiko/ubiquiti/unifi_switch_ssh.py @@ -0,0 +1,27 @@ +import time +from netmiko.ubiquiti.edge_ssh import UbiquitiEdgeSSH + + +class UnifiSwitchSSH(UbiquitiEdgeSSH): + def session_preparation(self): + """Prepare the session after the connection has been established. + When SSHing to a UniFi switch, the session initially starts at a Linux + shell. Nothing interesting can be done in this environment, however, + running `telnet localhost` drops the session to a more familiar + environment.""" + + self._test_channel_read() + self.set_base_prompt() + self.send_command( + command_string="telnet localhost", expect_string=r"\(UBNT\) >" + ) + self.set_base_prompt() + self.enable() + self.disable_paging() + + # Clear read buffer + time.sleep(0.3 * self.global_delay_factor) + self.clear_buffer() + + def disable_paging(self, command="terminal length 0"): + super(UbiquitiEdgeSSH, self).disable_paging(command=command) diff --git a/tests/etc/commands.yml.example b/tests/etc/commands.yml.example index 7f06e5c35..12743309e 100644 --- a/tests/etc/commands.yml.example +++ b/tests/etc/commands.yml.example @@ -129,6 +129,16 @@ ubiquiti_edge: - "logging persistent 4" config_verification: "show running-config | include 'logging'" +ubiquiti_unifi: + version: "show version" + basic: "show network" + extended_output: "show running-config" + config: + - "logging persistent 3" + - "no logging persistent" + - "logging persistent 4" + config_verification: "show running-config | include 'logging'" + dellos10: version: "show version" basic: "show ip interface brief" diff --git a/tests/etc/responses.yml.example b/tests/etc/responses.yml.example index 1da7517e8..34e1f7acd 100644 --- a/tests/etc/responses.yml.example +++ b/tests/etc/responses.yml.example @@ -79,6 +79,16 @@ ubiquiti_edge: cmd_response_init: "" cmd_response_final: "logging persistent 4" +ubiquiti_unifi: + base_prompt: "(UBNT) " + router_prompt: "(UBNT) >" + enable_prompt: "(UBNT) #" + interface_ip: 10.0.132.4 + version_banner: "Software Version" + multiple_line_output: "Current Configuration:" + cmd_response_init: "" + cmd_response_final: "logging persistent 4" + dellos10: base_prompt: OS10 router_prompt : OS10# diff --git a/tests/test_ubiquiti_unifi.sh b/tests/test_ubiquiti_unifi.sh new file mode 100755 index 000000000..0d7fc0b48 --- /dev/null +++ b/tests/test_ubiquiti_unifi.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +RETURN_CODE=0 + +# Exit on the first test failure and set RETURN_CODE = 1 +echo "Starting tests...good luck:" \ +&& echo "Ubiquiti UniFi Switch" \ +&& py.test -v test_netmiko_show.py --test_device ubiquiti_unifi \ +&& py.test -v test_netmiko_config.py --test_device ubiquiti_unifi \ +|| RETURN_CODE=1 + +exit $RETURN_CODE From 21cffda2b9ac3c68eb538379b327e1d454c01d2c Mon Sep 17 00:00:00 2001 From: NGUYEN Duy Cuong Date: Mon, 6 Jan 2020 23:23:51 +0800 Subject: [PATCH 02/39] Add `special_login_handler()` to ignore password change request. In Huawei devices that have password configured for the first time (no AAA is used), the devices will always ask for password change. The `special_login_handler()` ignores by answering "N" when such prompt appears (leaving the prompt unanswered for 15 seconds will do - but it is too long) --- netmiko/huawei/huawei.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/netmiko/huawei/huawei.py b/netmiko/huawei/huawei.py index 18e1d2fa8..f41cd9337 100644 --- a/netmiko/huawei/huawei.py +++ b/netmiko/huawei/huawei.py @@ -90,7 +90,15 @@ def save_config(self, cmd="save", confirm=True, confirm_response="y"): class HuaweiSSH(HuaweiBase): """Huawei SSH driver.""" - pass + def special_login_handler(self): + """Handle password change request by ignoring it""" + + password_change_prompt = r"(Change now|Please choose 'YES' or 'NO').+" + output = self.read_until_prompt_or_pattern(password_change_prompt) + if re.search(password_change_prompt, output): + self.write_channel("N\n") + self._read_channel_expect(pattern="N\n") + return output class HuaweiTelnet(HuaweiBase): From b3eb0f769fa32095e4056fc735d460fc68b84245 Mon Sep 17 00:00:00 2001 From: Brandon Spendlove Date: Fri, 20 Mar 2020 20:24:48 +0000 Subject: [PATCH 03/39] Cleaned and created branch for Huawei SmartAX --- netmiko/huawei/__init__.py | 3 +- netmiko/huawei/huawei.py | 2 - netmiko/huawei/huawei_smartax.py | 241 +++++++++++++++++++++++++++++ netmiko/ssh_dispatcher.py | 8 +- tests/etc/commands.yml.example | 21 +-- tests/etc/responses.yml.example | 24 +-- tests/etc/test_devices.yml.example | 18 +-- tests/test_huawei_smartax.sh | 7 + 8 files changed, 271 insertions(+), 53 deletions(-) create mode 100644 netmiko/huawei/huawei_smartax.py create mode 100644 tests/test_huawei_smartax.sh diff --git a/netmiko/huawei/__init__.py b/netmiko/huawei/__init__.py index 6b7d814d1..40b818c63 100644 --- a/netmiko/huawei/__init__.py +++ b/netmiko/huawei/__init__.py @@ -1,4 +1,5 @@ from netmiko.huawei.huawei import HuaweiSSH, HuaweiVrpv8SSH from netmiko.huawei.huawei import HuaweiTelnet +from netmiko.huawei.huawei_smartax import HuaweiSmartAXSSH -__all__ = ["HuaweiSSH", "HuaweiVrpv8SSH", "HuaweiTelnet"] +__all__ = ["HuaweiSmartAXSSH", "HuaweiSSH", "HuaweiVrpv8SSH", "HuaweiTelnet"] diff --git a/netmiko/huawei/huawei.py b/netmiko/huawei/huawei.py index c8d4ef201..fcb84029c 100644 --- a/netmiko/huawei/huawei.py +++ b/netmiko/huawei/huawei.py @@ -4,7 +4,6 @@ from netmiko.ssh_exception import NetmikoAuthenticationException from netmiko import log - class HuaweiBase(CiscoBaseConnection): def session_preparation(self): """Prepare the session after the connection has been established.""" @@ -104,7 +103,6 @@ def save_config(self, cmd="save", confirm=True, confirm_response="y"): cmd=cmd, confirm=confirm, confirm_response=confirm_response ) - class HuaweiSSH(HuaweiBase): """Huawei SSH driver.""" diff --git a/netmiko/huawei/huawei_smartax.py b/netmiko/huawei/huawei_smartax.py new file mode 100644 index 000000000..dd8f7c085 --- /dev/null +++ b/netmiko/huawei/huawei_smartax.py @@ -0,0 +1,241 @@ +import time +import re +from netmiko.cisco_base_connection import CiscoBaseConnection +from netmiko import log +from collections import deque + +class HuaweiSmartAXSSH(CiscoBaseConnection): + def session_preparation(self): + """Prepare the session after the connection has been established.""" + self._test_channel_read() + self.set_base_prompt() + self.disable_paging(command="scroll 512") + # Clear the read buffer + time.sleep(0.3 * self.global_delay_factor) + self.clear_buffer() + + def find_prompt(self, delay_factor=1): + """Finds the current network device prompt, last line only. + + :param delay_factor: See __init__: global_delay_factor + :type delay_factor: int + """ + delay_factor = self.select_delay_factor(delay_factor) + self.clear_buffer() + self.write_channel(self.RETURN) + sleep_time = delay_factor * 0.1 + time.sleep(sleep_time) + + # Initial attempt to get prompt + prompt = self.read_channel() + # Check if the only thing you received was a newline + count = 0 + prompt = prompt.strip() + while count <= 12 and not prompt: + prompt = self.read_channel().strip() + if not prompt: + time.sleep(sleep_time) + if sleep_time <= 3: + # Double the sleep_time when it is small + sleep_time *= 2 + else: + sleep_time += 1 + count += 1 + + # If multiple lines in the output take the last line + prompt = self.normalize_linefeeds(prompt) + prompt = prompt.split(self.RESPONSE_RETURN)[-1] + prompt = prompt.strip() + if not prompt: + raise ValueError(f"Unable to find prompt: {prompt}") + time.sleep(delay_factor * 0.1) + self.clear_buffer() + log.debug(f"[find_prompt()]: prompt is {prompt}") + return prompt + + def send_command_timing(self, command_string="", cmd_verify=False): + """ Sends Enter if the pattern { is found in the output.. This is required for SmartAX """ + output = super().send_command_timing(command_string=command_string) + + if r"{ " in output: + self.write_channel("\n") + + output += self.read_channel() + + return output + + def send_command_expect(self, *args, **kwargs): + """ Huawei SmartAX can't disable paging so I've handled output to send \n or space when required """ + return self.send_command(*args, **kwargs) + + def send_command( + self, + command_string, + expect_string=None, + delay_factor=1, + max_loops=500, + auto_find_prompt=True, + strip_prompt=True, + strip_command=True, + normalize=True, + cmd_verify=True, + ): + """Execute command_string on the SSH channel using a pattern-based mechanism. Generally + used for show commands. By default this method will keep waiting to receive data until the + network device prompt is detected. The current network device prompt will be determined + automatically. + + :param command_string: The command to be executed on the remote device. + :type command_string: str + + :param expect_string: Regular expression pattern to use for determining end of output. + If left blank will default to being based on router prompt. + :type expect_string: str + + :param delay_factor: Multiplying factor used to adjust delays (default: 1). + :type delay_factor: int + + :param max_loops: Controls wait time in conjunction with delay_factor. Will default to be + based upon self.timeout. + :type max_loops: int + + :param strip_prompt: Remove the trailing router prompt from the output (default: True). + :type strip_prompt: bool + + :param strip_command: Remove the echo of the command from the output (default: True). + :type strip_command: bool + + :param normalize: Ensure the proper enter is sent at end of command (default: True). + :type normalize: bool + + Huawei SmartAX can't disable paging so I've handled output to send \n or space when required depending on the output found... + + """ + # Time to delay in each read loop + loop_delay = 0.2 + + # Default to making loop time be roughly equivalent to self.timeout (support old max_loops + # and delay_factor arguments for backwards compatibility). + delay_factor = self.select_delay_factor(delay_factor) + if delay_factor == 1 and max_loops == 500: + # Default arguments are being used; use self.timeout instead + max_loops = int(self.timeout / loop_delay) + + # Find the current router prompt + if expect_string is None: + if auto_find_prompt: + try: + prompt = self.find_prompt(delay_factor=delay_factor) + except ValueError: + prompt = self.base_prompt + else: + prompt = self.base_prompt + search_pattern = re.escape(prompt.strip()) + else: + search_pattern = expect_string + + if normalize: + command_string = self.normalize_cmd(command_string) + + time.sleep(delay_factor * loop_delay) + self.clear_buffer() + self.write_channel(command_string) + new_data = "" + + cmd = command_string.strip() + # if cmd is just and "enter" skip this section + if cmd: + # Make sure you read until you detect the command echo (avoid getting out of sync) + new_data = self.read_until_pattern(pattern=re.escape(cmd)) + new_data = self.normalize_linefeeds(new_data) + # Strip off everything before the command echo (to avoid false positives on the prompt) + if new_data.count(cmd) == 1: + new_data = new_data.split(cmd)[1:] + new_data = self.RESPONSE_RETURN.join(new_data) + new_data = new_data.lstrip() + new_data = f"{cmd}{self.RESPONSE_RETURN}{new_data}" + + i = 1 + output = "" + past_three_reads = deque(maxlen=3) + first_line_processed = False + + # Keep reading data until search_pattern is found or until max_loops is reached. + while i <= max_loops: + if new_data: + output += new_data + past_three_reads.append(new_data) + + # Case where we haven't processed the first_line yet (there is a potential issue + # in the first line (in cases where the line is repainted). + if not first_line_processed: + output, first_line_processed = self._first_line_handler( + output, search_pattern + ) + # Check if we have already found our pattern + if re.search(search_pattern, output): + break + + else: + # Check if pattern is in the past three reads + if re.search(search_pattern, "".join(past_three_reads)): + break + + if r"{ " + enable_prompt: "ol01.test-lab.xyz#" + interface_ip: 192.0.2.1 + version_banner: "VERSION :" + multiple_line_output: "" diff --git a/tests/etc/test_devices.yml.example b/tests/etc/test_devices.yml.example index a47f17484..af20e1670 100644 --- a/tests/etc/test_devices.yml.example +++ b/tests/etc/test_devices.yml.example @@ -178,21 +178,15 @@ keymile_nos: username: TEST password: TEST -dlink_ds: - device_type: dlink_ds - ip: 192.168.50.10 - username: admin - password: admin - -dlink_ds_telnet: - device_type: dlink_ds_telnet - ip: 192.168.50.10 - username: admin - password: admin - ruijie_os: device_type: ruijie_os ip: 1.1.1.1 username: ruijie password: ruijie secret: ruijie + +huawei_smartax: + device_type: huawei_smartax + ip: 192.0.2.1 + username: TEST + password: TEST diff --git a/tests/test_huawei_smartax.sh b/tests/test_huawei_smartax.sh new file mode 100644 index 000000000..9a0910615 --- /dev/null +++ b/tests/test_huawei_smartax.sh @@ -0,0 +1,7 @@ +echo "Huawei SmartAX SSH" \ +&& date \ +&& py.test -v test_netmiko_show.py --test_device huawei_smartax \ +&& date \ +|| RETURN_CODE=1 + +exit $RETURN_CODE From 6f8dd3b60a363fc3973bc485dbe4b06d0d3d9935 Mon Sep 17 00:00:00 2001 From: Brandon Spendlove Date: Sat, 21 Mar 2020 22:47:22 +0000 Subject: [PATCH 04/39] Resolving minor issues --- netmiko/huawei/huawei_smartax.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/netmiko/huawei/huawei_smartax.py b/netmiko/huawei/huawei_smartax.py index dd8f7c085..abd943b9e 100644 --- a/netmiko/huawei/huawei_smartax.py +++ b/netmiko/huawei/huawei_smartax.py @@ -9,7 +9,7 @@ def session_preparation(self): """Prepare the session after the connection has been established.""" self._test_channel_read() self.set_base_prompt() - self.disable_paging(command="scroll 512") + self.disable_paging() # Clear the read buffer time.sleep(0.3 * self.global_delay_factor) self.clear_buffer() @@ -145,6 +145,7 @@ def send_command( cmd = command_string.strip() # if cmd is just and "enter" skip this section if cmd: + log.debug(f"cmd is: {cmd}") # Make sure you read until you detect the command echo (avoid getting out of sync) new_data = self.read_until_pattern(pattern=re.escape(cmd)) new_data = self.normalize_linefeeds(new_data) @@ -210,6 +211,17 @@ def send_command( return output + def disable_paging(self, command="scroll", delay_factor=1): + + delay_factor = self.select_delay_factor(delay_factor) + time.sleep(delay_factor * 0.1) + self.clear_buffer() + command = self.normalize_cmd(command) + log.debug("in disable_paging") + log.debug(f"Command: {command}") + self.send_command(command) #Can't use write_channel because { } output we need to handle on SmartAX Devices + log.debug("Exiting disable_paging") + def config_mode(self, config_command="config", pattern=""): """Enter configuration mode.""" if not pattern: From f2086585b9f9719f76230d9ccde904a6ca8f4093 Mon Sep 17 00:00:00 2001 From: Brandon Spendlove Date: Sun, 22 Mar 2020 22:54:46 +0000 Subject: [PATCH 05/39] Testing Huawei Output --- netmiko/huawei/huawei_smartax.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/netmiko/huawei/huawei_smartax.py b/netmiko/huawei/huawei_smartax.py index abd943b9e..39b7dbf75 100644 --- a/netmiko/huawei/huawei_smartax.py +++ b/netmiko/huawei/huawei_smartax.py @@ -147,6 +147,10 @@ def send_command( if cmd: log.debug(f"cmd is: {cmd}") # Make sure you read until you detect the command echo (avoid getting out of sync) + if r"{ " in self.read_channel(): + self.write_channel("\n") + time.sleep(delay_factor) + new_data = self.read_until_pattern(pattern=re.escape(cmd)) new_data = self.normalize_linefeeds(new_data) # Strip off everything before the command echo (to avoid false positives on the prompt) From 94968f807e50a9d6d4cc04c93d03922a0d89b9c6 Mon Sep 17 00:00:00 2001 From: Brandon Spendlove Date: Mon, 23 Mar 2020 18:02:59 +0000 Subject: [PATCH 06/39] Introduced disable_smart_interaction --- netmiko/huawei/huawei_smartax.old | 262 ++++++++++++++++++++++++++++++ netmiko/huawei/huawei_smartax.py | 229 ++------------------------ 2 files changed, 278 insertions(+), 213 deletions(-) create mode 100644 netmiko/huawei/huawei_smartax.old diff --git a/netmiko/huawei/huawei_smartax.old b/netmiko/huawei/huawei_smartax.old new file mode 100644 index 000000000..27c5325b4 --- /dev/null +++ b/netmiko/huawei/huawei_smartax.old @@ -0,0 +1,262 @@ +import time +import re +from netmiko.cisco_base_connection import CiscoBaseConnection +from netmiko import log +from collections import deque + +class HuaweiSmartAXSSH(CiscoBaseConnection): + def session_preparation(self): + """Prepare the session after the connection has been established.""" + self._test_channel_read() + self.set_base_prompt() + self.disable_paging() + # Clear the read buffer + time.sleep(0.3 * self.global_delay_factor) + self.clear_buffer() + + def find_prompt(self, delay_factor=1): + """Finds the current network device prompt, last line only. + + :param delay_factor: See __init__: global_delay_factor + :type delay_factor: int + """ + delay_factor = self.select_delay_factor(delay_factor) + self.clear_buffer() + self.write_channel(self.RETURN) + sleep_time = delay_factor * 0.1 + time.sleep(sleep_time) + + # Initial attempt to get prompt + prompt = self.read_channel() + # Check if the only thing you received was a newline + count = 0 + prompt = prompt.strip() + while count <= 12 and not prompt: + prompt = self.read_channel().strip() + if not prompt: + time.sleep(sleep_time) + if sleep_time <= 3: + # Double the sleep_time when it is small + sleep_time *= 2 + else: + sleep_time += 1 + count += 1 + + # If multiple lines in the output take the last line + prompt = self.normalize_linefeeds(prompt) + prompt = prompt.split(self.RESPONSE_RETURN)[-1] + prompt = prompt.strip() + if not prompt: + raise ValueError(f"Unable to find prompt: {prompt}") + time.sleep(delay_factor * 0.1) + self.clear_buffer() + log.debug(f"[find_prompt()]: prompt is {prompt}") + return prompt + + def send_command_timing(self, command_string="", cmd_verify=False): + """ Sends Enter if the pattern { is found in the output.. This is required for SmartAX """ + output = super().send_command_timing(command_string=command_string) + + if r"{ " in output: + self.write_channel("\n") + + output += self.read_channel() + + return output + + def send_command_expect(self, *args, **kwargs): + """ Huawei SmartAX can't disable paging so I've handled output to send \n or space when required """ + return self.send_command(*args, **kwargs) + + def send_command( + self, + command_string, + expect_string=None, + delay_factor=1, + max_loops=500, + auto_find_prompt=True, + strip_prompt=True, + strip_command=True, + normalize=True, + cmd_verify=True, + ): + """Execute command_string on the SSH channel using a pattern-based mechanism. Generally + used for show commands. By default this method will keep waiting to receive data until the + network device prompt is detected. The current network device prompt will be determined + automatically. + + :param command_string: The command to be executed on the remote device. + :type command_string: str + + :param expect_string: Regular expression pattern to use for determining end of output. + If left blank will default to being based on router prompt. + :type expect_string: str + + :param delay_factor: Multiplying factor used to adjust delays (default: 1). + :type delay_factor: int + + :param max_loops: Controls wait time in conjunction with delay_factor. Will default to be + based upon self.timeout. + :type max_loops: int + + :param strip_prompt: Remove the trailing router prompt from the output (default: True). + :type strip_prompt: bool + + :param strip_command: Remove the echo of the command from the output (default: True). + :type strip_command: bool + + :param normalize: Ensure the proper enter is sent at end of command (default: True). + :type normalize: bool + + Huawei SmartAX can't disable paging so I've handled output to send \n or space when required depending on the output found... + + """ + # Time to delay in each read loop + loop_delay = 0.2 + + # Default to making loop time be roughly equivalent to self.timeout (support old max_loops + # and delay_factor arguments for backwards compatibility). + delay_factor = self.select_delay_factor(delay_factor) + if delay_factor == 1 and max_loops == 500: + # Default arguments are being used; use self.timeout instead + max_loops = int(self.timeout / loop_delay) + + # Find the current router prompt + if expect_string is None: + if auto_find_prompt: + try: + prompt = self.find_prompt(delay_factor=delay_factor) + except ValueError: + prompt = self.base_prompt + else: + prompt = self.base_prompt + search_pattern = re.escape(prompt.strip()) + else: + search_pattern = expect_string + + if normalize: + command_string = self.normalize_cmd(command_string) + + time.sleep(delay_factor * loop_delay) + self.clear_buffer() + self.write_channel(command_string) + new_data = "" + + cmd = command_string.strip() + # if cmd is just and "enter" skip this section + if cmd: + log.debug(f"cmd is: {cmd}") + # Make sure you read until you detect the command echo (avoid getting out of sync) + if r"{ " in self.read_channel(): + self.write_channel("\n") + time.sleep(delay_factor) + + new_data = self.read_until_pattern(pattern=re.escape(cmd)) + new_data = self.normalize_linefeeds(new_data) + # Strip off everything before the command echo (to avoid false positives on the prompt) + if new_data.count(cmd) == 1: + new_data = new_data.split(cmd)[1:] + new_data = self.RESPONSE_RETURN.join(new_data) + new_data = new_data.lstrip() + new_data = f"{cmd}{self.RESPONSE_RETURN}{new_data}" + + i = 1 + output = "" + past_three_reads = deque(maxlen=3) + first_line_processed = False + + # Keep reading data until search_pattern is found or until max_loops is reached. + while i <= max_loops: + if new_data: + output += new_data + past_three_reads.append(new_data) + + # Case where we haven't processed the first_line yet (there is a potential issue + # in the first line (in cases where the line is repainted). + if not first_line_processed: + output, first_line_processed = self._first_line_handler( + output, search_pattern + ) + # Check if we have already found our pattern + if re.search(search_pattern, output): + break + + else: + # Check if pattern is in the past three reads + if re.search(search_pattern, "".join(past_three_reads)): + break + + if r"{ } output we need to handle on SmartAX Devices + time.sleep(delay_factor) + """ + if r"{ " in self.read_channel(): + self.write_channel(self.RETURN) + """ + log.debug("Exiting disable_paging") + + def config_mode(self, config_command="config", pattern=""): + """Enter configuration mode.""" + if not pattern: + pattern = re.escape(self.base_prompt[:16]) + return super().config_mode(config_command=config_command, pattern=pattern) + + def check_config_mode(self, check_string=")#", pattern="#"): + return super().check_config_mode(check_string=check_string, pattern=pattern) # pattern + + def exit_config_mode(self, exit_config="quit"): + return super().exit_config_mode(exit_config=exit_config) + + def check_enable_mode(self, check_string="#"): + return super().check_enable_mode(check_string=check_string) + + def enable(self, cmd="enable", pattern="", re_flags=re.IGNORECASE): + return super().enable(cmd=cmd, pattern=pattern, re_flags=re_flags) # pattern + + def exit_enable_mode(self, exit_command="disable"): + return super().exit_enable_mode(exit_command=exit_command) + + def set_base_prompt(self, pri_prompt_terminator=">", alt_prompt_terminator="#"): + return super().set_base_prompt(pri_prompt_terminator=pri_prompt_terminator, alt_prompt_terminator=alt_prompt_terminator) + + def save_config(self, cmd="save", confirm=False, confirm_response=""): + """ Save Config for HuaweiSSH""" + return super().save_config( + cmd=cmd, confirm=confirm, confirm_response=confirm_response + ) diff --git a/netmiko/huawei/huawei_smartax.py b/netmiko/huawei/huawei_smartax.py index 39b7dbf75..9c3bb1d87 100644 --- a/netmiko/huawei/huawei_smartax.py +++ b/netmiko/huawei/huawei_smartax.py @@ -1,239 +1,42 @@ import time import re from netmiko.cisco_base_connection import CiscoBaseConnection +from netmiko.base_connection import BaseConnection from netmiko import log -from collections import deque -class HuaweiSmartAXSSH(CiscoBaseConnection): +class HuaweiSmartAXSSH(BaseConnection): def session_preparation(self): """Prepare the session after the connection has been established.""" self._test_channel_read() self.set_base_prompt() + self.disable_smart_interaction() self.disable_paging() # Clear the read buffer time.sleep(0.3 * self.global_delay_factor) self.clear_buffer() - def find_prompt(self, delay_factor=1): - """Finds the current network device prompt, last line only. - - :param delay_factor: See __init__: global_delay_factor - :type delay_factor: int - """ - delay_factor = self.select_delay_factor(delay_factor) - self.clear_buffer() - self.write_channel(self.RETURN) - sleep_time = delay_factor * 0.1 - time.sleep(sleep_time) - - # Initial attempt to get prompt - prompt = self.read_channel() - # Check if the only thing you received was a newline - count = 0 - prompt = prompt.strip() - while count <= 12 and not prompt: - prompt = self.read_channel().strip() - if not prompt: - time.sleep(sleep_time) - if sleep_time <= 3: - # Double the sleep_time when it is small - sleep_time *= 2 - else: - sleep_time += 1 - count += 1 - - # If multiple lines in the output take the last line - prompt = self.normalize_linefeeds(prompt) - prompt = prompt.split(self.RESPONSE_RETURN)[-1] - prompt = prompt.strip() - if not prompt: - raise ValueError(f"Unable to find prompt: {prompt}") - time.sleep(delay_factor * 0.1) - self.clear_buffer() - log.debug(f"[find_prompt()]: prompt is {prompt}") - return prompt - - def send_command_timing(self, command_string="", cmd_verify=False): - """ Sends Enter if the pattern { is found in the output.. This is required for SmartAX """ - output = super().send_command_timing(command_string=command_string) - - if r"{ " in output: - self.write_channel("\n") - - output += self.read_channel() - - return output - - def send_command_expect(self, *args, **kwargs): - """ Huawei SmartAX can't disable paging so I've handled output to send \n or space when required """ - return self.send_command(*args, **kwargs) - - def send_command( - self, - command_string, - expect_string=None, - delay_factor=1, - max_loops=500, - auto_find_prompt=True, - strip_prompt=True, - strip_command=True, - normalize=True, - cmd_verify=True, - ): - """Execute command_string on the SSH channel using a pattern-based mechanism. Generally - used for show commands. By default this method will keep waiting to receive data until the - network device prompt is detected. The current network device prompt will be determined - automatically. - - :param command_string: The command to be executed on the remote device. - :type command_string: str - - :param expect_string: Regular expression pattern to use for determining end of output. - If left blank will default to being based on router prompt. - :type expect_string: str - - :param delay_factor: Multiplying factor used to adjust delays (default: 1). - :type delay_factor: int - - :param max_loops: Controls wait time in conjunction with delay_factor. Will default to be - based upon self.timeout. - :type max_loops: int - - :param strip_prompt: Remove the trailing router prompt from the output (default: True). - :type strip_prompt: bool - - :param strip_command: Remove the echo of the command from the output (default: True). - :type strip_command: bool - - :param normalize: Ensure the proper enter is sent at end of command (default: True). - :type normalize: bool - - Huawei SmartAX can't disable paging so I've handled output to send \n or space when required depending on the output found... - - """ - # Time to delay in each read loop - loop_delay = 0.2 - - # Default to making loop time be roughly equivalent to self.timeout (support old max_loops - # and delay_factor arguments for backwards compatibility). - delay_factor = self.select_delay_factor(delay_factor) - if delay_factor == 1 and max_loops == 500: - # Default arguments are being used; use self.timeout instead - max_loops = int(self.timeout / loop_delay) - - # Find the current router prompt - if expect_string is None: - if auto_find_prompt: - try: - prompt = self.find_prompt(delay_factor=delay_factor) - except ValueError: - prompt = self.base_prompt - else: - prompt = self.base_prompt - search_pattern = re.escape(prompt.strip()) - else: - search_pattern = expect_string - - if normalize: - command_string = self.normalize_cmd(command_string) - - time.sleep(delay_factor * loop_delay) - self.clear_buffer() - self.write_channel(command_string) - new_data = "" - - cmd = command_string.strip() - # if cmd is just and "enter" skip this section - if cmd: - log.debug(f"cmd is: {cmd}") - # Make sure you read until you detect the command echo (avoid getting out of sync) - if r"{ " in self.read_channel(): - self.write_channel("\n") - time.sleep(delay_factor) - - new_data = self.read_until_pattern(pattern=re.escape(cmd)) - new_data = self.normalize_linefeeds(new_data) - # Strip off everything before the command echo (to avoid false positives on the prompt) - if new_data.count(cmd) == 1: - new_data = new_data.split(cmd)[1:] - new_data = self.RESPONSE_RETURN.join(new_data) - new_data = new_data.lstrip() - new_data = f"{cmd}{self.RESPONSE_RETURN}{new_data}" - - i = 1 - output = "" - past_three_reads = deque(maxlen=3) - first_line_processed = False - - # Keep reading data until search_pattern is found or until max_loops is reached. - while i <= max_loops: - if new_data: - output += new_data - past_three_reads.append(new_data) - - # Case where we haven't processed the first_line yet (there is a potential issue - # in the first line (in cases where the line is repainted). - if not first_line_processed: - output, first_line_processed = self._first_line_handler( - output, search_pattern - ) - # Check if we have already found our pattern - if re.search(search_pattern, output): - break - - else: - # Check if pattern is in the past three reads - if re.search(search_pattern, "".join(past_three_reads)): - break - - if r"{ } prompt to avoid having to sent a 2nd return after each command""" delay_factor = self.select_delay_factor(delay_factor) time.sleep(delay_factor * 0.1) self.clear_buffer() command = self.normalize_cmd(command) - log.debug("in disable_paging") + log.debug("In disable_smart_interaction") log.debug(f"Command: {command}") - self.send_command(command) #Can't use write_channel because { } output we need to handle on SmartAX Devices - log.debug("Exiting disable_paging") + self.write_channel(command) + output = self.read_until_pattern(pattern=re.escape(command.strip())) + log.debug(f"{output}") + log.debug("Exiting disable_paging") + + def disable_paging(self, command="scroll"): + return super().disable_paging(command=command) def config_mode(self, config_command="config", pattern=""): """Enter configuration mode.""" - if not pattern: - pattern = re.escape(self.base_prompt[:16]) return super().config_mode(config_command=config_command, pattern=pattern) - def check_config_mode(self, check_string=")#", pattern="#"): - return super().check_config_mode(check_string=check_string, pattern=pattern) # pattern + def check_config_mode(self, check_string=")#", pattern=""): + return super().check_config_mode(check_string=check_string, pattern=pattern) def exit_config_mode(self, exit_config="quit"): return super().exit_config_mode(exit_config=exit_config) @@ -242,7 +45,7 @@ def check_enable_mode(self, check_string="#"): return super().check_enable_mode(check_string=check_string) def enable(self, cmd="enable", pattern="", re_flags=re.IGNORECASE): - return super().enable(cmd=cmd, pattern=pattern, re_flags=re_flags) # pattern + return super().enable(cmd=cmd, pattern=pattern, re_flags=re_flags) def exit_enable_mode(self, exit_command="disable"): return super().exit_enable_mode(exit_command=exit_command) From a05620391701262e22c5dac0553542e7e30ede07 Mon Sep 17 00:00:00 2001 From: Brandon Spendlove Date: Mon, 23 Mar 2020 18:03:35 +0000 Subject: [PATCH 07/39] Introduced disable_smart_interaction --- netmiko/huawei/huawei_smartax.old | 262 ------------------------------ 1 file changed, 262 deletions(-) delete mode 100644 netmiko/huawei/huawei_smartax.old diff --git a/netmiko/huawei/huawei_smartax.old b/netmiko/huawei/huawei_smartax.old deleted file mode 100644 index 27c5325b4..000000000 --- a/netmiko/huawei/huawei_smartax.old +++ /dev/null @@ -1,262 +0,0 @@ -import time -import re -from netmiko.cisco_base_connection import CiscoBaseConnection -from netmiko import log -from collections import deque - -class HuaweiSmartAXSSH(CiscoBaseConnection): - def session_preparation(self): - """Prepare the session after the connection has been established.""" - self._test_channel_read() - self.set_base_prompt() - self.disable_paging() - # Clear the read buffer - time.sleep(0.3 * self.global_delay_factor) - self.clear_buffer() - - def find_prompt(self, delay_factor=1): - """Finds the current network device prompt, last line only. - - :param delay_factor: See __init__: global_delay_factor - :type delay_factor: int - """ - delay_factor = self.select_delay_factor(delay_factor) - self.clear_buffer() - self.write_channel(self.RETURN) - sleep_time = delay_factor * 0.1 - time.sleep(sleep_time) - - # Initial attempt to get prompt - prompt = self.read_channel() - # Check if the only thing you received was a newline - count = 0 - prompt = prompt.strip() - while count <= 12 and not prompt: - prompt = self.read_channel().strip() - if not prompt: - time.sleep(sleep_time) - if sleep_time <= 3: - # Double the sleep_time when it is small - sleep_time *= 2 - else: - sleep_time += 1 - count += 1 - - # If multiple lines in the output take the last line - prompt = self.normalize_linefeeds(prompt) - prompt = prompt.split(self.RESPONSE_RETURN)[-1] - prompt = prompt.strip() - if not prompt: - raise ValueError(f"Unable to find prompt: {prompt}") - time.sleep(delay_factor * 0.1) - self.clear_buffer() - log.debug(f"[find_prompt()]: prompt is {prompt}") - return prompt - - def send_command_timing(self, command_string="", cmd_verify=False): - """ Sends Enter if the pattern { is found in the output.. This is required for SmartAX """ - output = super().send_command_timing(command_string=command_string) - - if r"{ " in output: - self.write_channel("\n") - - output += self.read_channel() - - return output - - def send_command_expect(self, *args, **kwargs): - """ Huawei SmartAX can't disable paging so I've handled output to send \n or space when required """ - return self.send_command(*args, **kwargs) - - def send_command( - self, - command_string, - expect_string=None, - delay_factor=1, - max_loops=500, - auto_find_prompt=True, - strip_prompt=True, - strip_command=True, - normalize=True, - cmd_verify=True, - ): - """Execute command_string on the SSH channel using a pattern-based mechanism. Generally - used for show commands. By default this method will keep waiting to receive data until the - network device prompt is detected. The current network device prompt will be determined - automatically. - - :param command_string: The command to be executed on the remote device. - :type command_string: str - - :param expect_string: Regular expression pattern to use for determining end of output. - If left blank will default to being based on router prompt. - :type expect_string: str - - :param delay_factor: Multiplying factor used to adjust delays (default: 1). - :type delay_factor: int - - :param max_loops: Controls wait time in conjunction with delay_factor. Will default to be - based upon self.timeout. - :type max_loops: int - - :param strip_prompt: Remove the trailing router prompt from the output (default: True). - :type strip_prompt: bool - - :param strip_command: Remove the echo of the command from the output (default: True). - :type strip_command: bool - - :param normalize: Ensure the proper enter is sent at end of command (default: True). - :type normalize: bool - - Huawei SmartAX can't disable paging so I've handled output to send \n or space when required depending on the output found... - - """ - # Time to delay in each read loop - loop_delay = 0.2 - - # Default to making loop time be roughly equivalent to self.timeout (support old max_loops - # and delay_factor arguments for backwards compatibility). - delay_factor = self.select_delay_factor(delay_factor) - if delay_factor == 1 and max_loops == 500: - # Default arguments are being used; use self.timeout instead - max_loops = int(self.timeout / loop_delay) - - # Find the current router prompt - if expect_string is None: - if auto_find_prompt: - try: - prompt = self.find_prompt(delay_factor=delay_factor) - except ValueError: - prompt = self.base_prompt - else: - prompt = self.base_prompt - search_pattern = re.escape(prompt.strip()) - else: - search_pattern = expect_string - - if normalize: - command_string = self.normalize_cmd(command_string) - - time.sleep(delay_factor * loop_delay) - self.clear_buffer() - self.write_channel(command_string) - new_data = "" - - cmd = command_string.strip() - # if cmd is just and "enter" skip this section - if cmd: - log.debug(f"cmd is: {cmd}") - # Make sure you read until you detect the command echo (avoid getting out of sync) - if r"{ " in self.read_channel(): - self.write_channel("\n") - time.sleep(delay_factor) - - new_data = self.read_until_pattern(pattern=re.escape(cmd)) - new_data = self.normalize_linefeeds(new_data) - # Strip off everything before the command echo (to avoid false positives on the prompt) - if new_data.count(cmd) == 1: - new_data = new_data.split(cmd)[1:] - new_data = self.RESPONSE_RETURN.join(new_data) - new_data = new_data.lstrip() - new_data = f"{cmd}{self.RESPONSE_RETURN}{new_data}" - - i = 1 - output = "" - past_three_reads = deque(maxlen=3) - first_line_processed = False - - # Keep reading data until search_pattern is found or until max_loops is reached. - while i <= max_loops: - if new_data: - output += new_data - past_three_reads.append(new_data) - - # Case where we haven't processed the first_line yet (there is a potential issue - # in the first line (in cases where the line is repainted). - if not first_line_processed: - output, first_line_processed = self._first_line_handler( - output, search_pattern - ) - # Check if we have already found our pattern - if re.search(search_pattern, output): - break - - else: - # Check if pattern is in the past three reads - if re.search(search_pattern, "".join(past_three_reads)): - break - - if r"{ } output we need to handle on SmartAX Devices - time.sleep(delay_factor) - """ - if r"{ " in self.read_channel(): - self.write_channel(self.RETURN) - """ - log.debug("Exiting disable_paging") - - def config_mode(self, config_command="config", pattern=""): - """Enter configuration mode.""" - if not pattern: - pattern = re.escape(self.base_prompt[:16]) - return super().config_mode(config_command=config_command, pattern=pattern) - - def check_config_mode(self, check_string=")#", pattern="#"): - return super().check_config_mode(check_string=check_string, pattern=pattern) # pattern - - def exit_config_mode(self, exit_config="quit"): - return super().exit_config_mode(exit_config=exit_config) - - def check_enable_mode(self, check_string="#"): - return super().check_enable_mode(check_string=check_string) - - def enable(self, cmd="enable", pattern="", re_flags=re.IGNORECASE): - return super().enable(cmd=cmd, pattern=pattern, re_flags=re_flags) # pattern - - def exit_enable_mode(self, exit_command="disable"): - return super().exit_enable_mode(exit_command=exit_command) - - def set_base_prompt(self, pri_prompt_terminator=">", alt_prompt_terminator="#"): - return super().set_base_prompt(pri_prompt_terminator=pri_prompt_terminator, alt_prompt_terminator=alt_prompt_terminator) - - def save_config(self, cmd="save", confirm=False, confirm_response=""): - """ Save Config for HuaweiSSH""" - return super().save_config( - cmd=cmd, confirm=confirm, confirm_response=confirm_response - ) From af5e4d76ac29e52d6676a6e8526e145ebcab3b8f Mon Sep 17 00:00:00 2001 From: Brandon Spendlove Date: Wed, 25 Mar 2020 00:45:23 +0000 Subject: [PATCH 08/39] Copied HuaweiBase ansi_escape_codes --- netmiko/huawei/huawei_smartax.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/netmiko/huawei/huawei_smartax.py b/netmiko/huawei/huawei_smartax.py index 9c3bb1d87..11b90b0db 100644 --- a/netmiko/huawei/huawei_smartax.py +++ b/netmiko/huawei/huawei_smartax.py @@ -1,12 +1,12 @@ import time import re from netmiko.cisco_base_connection import CiscoBaseConnection -from netmiko.base_connection import BaseConnection from netmiko import log -class HuaweiSmartAXSSH(BaseConnection): +class HuaweiSmartAXSSH(CiscoBaseConnection): def session_preparation(self): """Prepare the session after the connection has been established.""" + self.ansi_escape_codes = True self._test_channel_read() self.set_base_prompt() self.disable_smart_interaction() @@ -15,6 +15,22 @@ def session_preparation(self): time.sleep(0.3 * self.global_delay_factor) self.clear_buffer() + def strip_ansi_escape_codes(self, string_buffer): + """ + Huawei does a strange thing where they add a space and then add ESC[1D + to move the cursor to the left one. + The extra space is problematic. + """ + code_cursor_left = chr(27) + r"\[\d+D" + output = string_buffer + pattern = rf" {code_cursor_left}" + output = re.sub(pattern, "", output) + + log.debug("Stripping ANSI escape codes") + log.debug(f"new_output = {output}") + log.debug(f"repr = {repr(output)}") + return super().strip_ansi_escape_codes(output) + def disable_smart_interaction(self, command="undo smart", delay_factor=1): """Disables the { } prompt to avoid having to sent a 2nd return after each command""" delay_factor = self.select_delay_factor(delay_factor) @@ -26,7 +42,7 @@ def disable_smart_interaction(self, command="undo smart", delay_factor=1): self.write_channel(command) output = self.read_until_pattern(pattern=re.escape(command.strip())) log.debug(f"{output}") - log.debug("Exiting disable_paging") + log.debug("Exiting disable_smart_interaction") def disable_paging(self, command="scroll"): return super().disable_paging(command=command) @@ -35,10 +51,10 @@ def config_mode(self, config_command="config", pattern=""): """Enter configuration mode.""" return super().config_mode(config_command=config_command, pattern=pattern) - def check_config_mode(self, check_string=")#", pattern=""): - return super().check_config_mode(check_string=check_string, pattern=pattern) + def check_config_mode(self, check_string=")#"): + return super().check_config_mode(check_string=check_string) - def exit_config_mode(self, exit_config="quit"): + def exit_config_mode(self, exit_config="return"): return super().exit_config_mode(exit_config=exit_config) def check_enable_mode(self, check_string="#"): From d251d380847143e806e82f7888d5e717ebd3aab7 Mon Sep 17 00:00:00 2001 From: Brandon Spendlove Date: Wed, 25 Mar 2020 09:38:13 +0000 Subject: [PATCH 09/39] Updated commands.yml.example --- tests/etc/commands.yml.example | 3 +++ tests/py.test.log | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 tests/py.test.log diff --git a/tests/etc/commands.yml.example b/tests/etc/commands.yml.example index 677ca9ca0..2204be355 100644 --- a/tests/etc/commands.yml.example +++ b/tests/etc/commands.yml.example @@ -299,3 +299,6 @@ huawei_smartax: version: "display version" basic: "display system sys-info" extended_output: "display version" + config: + - acl 2456 + config_verification: "display current-configuration | include acl 2456" diff --git a/tests/py.test.log b/tests/py.test.log new file mode 100644 index 000000000..94e488234 --- /dev/null +++ b/tests/py.test.log @@ -0,0 +1,9 @@ +============================= test session starts ============================== +platform linux -- Python 3.6.8, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 -- /home/bspendlove/dev/gitlab/cfl_netmiko/venv_netmiko/bin/python3 +cachedir: .pytest_cache +rootdir: /home/bspendlove/dev/gitlab/cfl_netmiko/netmiko, inifile: setup.cfg +collecting ... collected 8 items + +test_netmiko_config.py::test_ssh_connect + +============================ no tests ran in 1.85s ============================= From 59cd15557e2ae898b1407d1a33a63afbff260d8a Mon Sep 17 00:00:00 2001 From: Brandon Date: Wed, 25 Mar 2020 09:39:16 +0000 Subject: [PATCH 10/39] Delete py.test.log --- tests/py.test.log | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 tests/py.test.log diff --git a/tests/py.test.log b/tests/py.test.log deleted file mode 100644 index 94e488234..000000000 --- a/tests/py.test.log +++ /dev/null @@ -1,9 +0,0 @@ -============================= test session starts ============================== -platform linux -- Python 3.6.8, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 -- /home/bspendlove/dev/gitlab/cfl_netmiko/venv_netmiko/bin/python3 -cachedir: .pytest_cache -rootdir: /home/bspendlove/dev/gitlab/cfl_netmiko/netmiko, inifile: setup.cfg -collecting ... collected 8 items - -test_netmiko_config.py::test_ssh_connect - -============================ no tests ran in 1.85s ============================= From fedf135ed805efafb288b7b69b58c5bf014f44c5 Mon Sep 17 00:00:00 2001 From: Brandon Spendlove Date: Wed, 25 Mar 2020 12:45:41 +0000 Subject: [PATCH 11/39] Fixed pep8 issues --- netmiko/huawei/huawei.py | 2 ++ netmiko/huawei/huawei_smartax.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/netmiko/huawei/huawei.py b/netmiko/huawei/huawei.py index fcb84029c..c8d4ef201 100644 --- a/netmiko/huawei/huawei.py +++ b/netmiko/huawei/huawei.py @@ -4,6 +4,7 @@ from netmiko.ssh_exception import NetmikoAuthenticationException from netmiko import log + class HuaweiBase(CiscoBaseConnection): def session_preparation(self): """Prepare the session after the connection has been established.""" @@ -103,6 +104,7 @@ def save_config(self, cmd="save", confirm=True, confirm_response="y"): cmd=cmd, confirm=confirm, confirm_response=confirm_response ) + class HuaweiSSH(HuaweiBase): """Huawei SSH driver.""" diff --git a/netmiko/huawei/huawei_smartax.py b/netmiko/huawei/huawei_smartax.py index 11b90b0db..32731dcce 100644 --- a/netmiko/huawei/huawei_smartax.py +++ b/netmiko/huawei/huawei_smartax.py @@ -3,6 +3,7 @@ from netmiko.cisco_base_connection import CiscoBaseConnection from netmiko import log + class HuaweiSmartAXSSH(CiscoBaseConnection): def session_preparation(self): """Prepare the session after the connection has been established.""" @@ -43,7 +44,7 @@ def disable_smart_interaction(self, command="undo smart", delay_factor=1): output = self.read_until_pattern(pattern=re.escape(command.strip())) log.debug(f"{output}") log.debug("Exiting disable_smart_interaction") - + def disable_paging(self, command="scroll"): return super().disable_paging(command=command) @@ -66,8 +67,13 @@ def enable(self, cmd="enable", pattern="", re_flags=re.IGNORECASE): def exit_enable_mode(self, exit_command="disable"): return super().exit_enable_mode(exit_command=exit_command) - def set_base_prompt(self, pri_prompt_terminator=">", alt_prompt_terminator="#"): - return super().set_base_prompt(pri_prompt_terminator=pri_prompt_terminator, alt_prompt_terminator=alt_prompt_terminator) + def set_base_prompt( + self, pri_prompt_terminator=">", alt_prompt_terminator="#" + ): + return super().set_base_prompt( + pri_prompt_iterminator=pri_prompt_terminator, + alt_prompt_terminator=alt_prompt_terminator + ) def save_config(self, cmd="save", confirm=False, confirm_response=""): """ Save Config for HuaweiSSH""" From b31ff9e12cec7d3c8dea6635cd206a02871907de Mon Sep 17 00:00:00 2001 From: Brandon Spendlove Date: Wed, 25 Mar 2020 12:56:01 +0000 Subject: [PATCH 12/39] Fixed black format error --- netmiko/huawei/huawei_smartax.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/netmiko/huawei/huawei_smartax.py b/netmiko/huawei/huawei_smartax.py index 32731dcce..c002e4a52 100644 --- a/netmiko/huawei/huawei_smartax.py +++ b/netmiko/huawei/huawei_smartax.py @@ -67,12 +67,10 @@ def enable(self, cmd="enable", pattern="", re_flags=re.IGNORECASE): def exit_enable_mode(self, exit_command="disable"): return super().exit_enable_mode(exit_command=exit_command) - def set_base_prompt( - self, pri_prompt_terminator=">", alt_prompt_terminator="#" - ): + def set_base_prompt(self, pri_prompt_terminator=">", alt_prompt_terminator="#"): return super().set_base_prompt( pri_prompt_iterminator=pri_prompt_terminator, - alt_prompt_terminator=alt_prompt_terminator + alt_prompt_terminator=alt_prompt_terminator, ) def save_config(self, cmd="save", confirm=False, confirm_response=""): From 08df787db6af348218c3d07cbc4466c76af302e3 Mon Sep 17 00:00:00 2001 From: Brandon Spendlove Date: Wed, 25 Mar 2020 21:16:16 +0000 Subject: [PATCH 13/39] Corrected mistype for set_base_prompt --- netmiko/huawei/huawei_smartax.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netmiko/huawei/huawei_smartax.py b/netmiko/huawei/huawei_smartax.py index c002e4a52..46bd83fec 100644 --- a/netmiko/huawei/huawei_smartax.py +++ b/netmiko/huawei/huawei_smartax.py @@ -69,7 +69,7 @@ def exit_enable_mode(self, exit_command="disable"): def set_base_prompt(self, pri_prompt_terminator=">", alt_prompt_terminator="#"): return super().set_base_prompt( - pri_prompt_iterminator=pri_prompt_terminator, + pri_prompt_terminator=pri_prompt_terminator, alt_prompt_terminator=alt_prompt_terminator, ) From 0c04b1eeacb321f249ac4acfe2eec4b15ee05ed4 Mon Sep 17 00:00:00 2001 From: "brandon.spendlove" Date: Thu, 26 Mar 2020 16:59:36 +0000 Subject: [PATCH 14/39] Changed method to private and deleted exit_enable_mode function --- netmiko/huawei/huawei_smartax.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/netmiko/huawei/huawei_smartax.py b/netmiko/huawei/huawei_smartax.py index 46bd83fec..0b915bb38 100644 --- a/netmiko/huawei/huawei_smartax.py +++ b/netmiko/huawei/huawei_smartax.py @@ -10,7 +10,7 @@ def session_preparation(self): self.ansi_escape_codes = True self._test_channel_read() self.set_base_prompt() - self.disable_smart_interaction() + self._disable_smart_interaction() self.disable_paging() # Clear the read buffer time.sleep(0.3 * self.global_delay_factor) @@ -32,7 +32,7 @@ def strip_ansi_escape_codes(self, string_buffer): log.debug(f"repr = {repr(output)}") return super().strip_ansi_escape_codes(output) - def disable_smart_interaction(self, command="undo smart", delay_factor=1): + def _disable_smart_interaction(self, command="undo smart", delay_factor=1): """Disables the { } prompt to avoid having to sent a 2nd return after each command""" delay_factor = self.select_delay_factor(delay_factor) time.sleep(delay_factor * 0.1) @@ -64,9 +64,6 @@ def check_enable_mode(self, check_string="#"): def enable(self, cmd="enable", pattern="", re_flags=re.IGNORECASE): return super().enable(cmd=cmd, pattern=pattern, re_flags=re_flags) - def exit_enable_mode(self, exit_command="disable"): - return super().exit_enable_mode(exit_command=exit_command) - def set_base_prompt(self, pri_prompt_terminator=">", alt_prompt_terminator="#"): return super().set_base_prompt( pri_prompt_terminator=pri_prompt_terminator, From 1cc483a70cc2bf99bf8791c42c5976cf653674ef Mon Sep 17 00:00:00 2001 From: "brandon.spendlove" Date: Thu, 26 Mar 2020 17:32:35 +0000 Subject: [PATCH 15/39] Fixed issues with out of sync files --- netmiko/ssh_dispatcher.py | 6 ++++++ tests/etc/commands.yml.example | 16 ++++++++++++++++ tests/etc/responses.yml.example | 16 ++++++++++++++++ tests/etc/test_devices.yml.example | 12 ++++++++++++ 4 files changed, 50 insertions(+) diff --git a/netmiko/ssh_dispatcher.py b/netmiko/ssh_dispatcher.py index 9483e4602..3deafc622 100644 --- a/netmiko/ssh_dispatcher.py +++ b/netmiko/ssh_dispatcher.py @@ -31,6 +31,7 @@ from netmiko.dell import DellPowerConnectSSH from netmiko.dell import DellPowerConnectTelnet from netmiko.dell import DellIsilonSSH +from netmiko.dlink import DlinkDSTelnet, DlinkDSSSH from netmiko.eltex import EltexSSH, EltexEsrSSH from netmiko.endace import EndaceSSH from netmiko.enterasys import EnterasysSSH @@ -78,6 +79,8 @@ from netmiko.terminal_server import TerminalServerTelnet from netmiko.ubiquiti import UbiquitiEdgeSSH from netmiko.vyos import VyOSSSH +from netmiko.watchguard import WatchguardFirewareSSH + # The keys of this dictionary are the supported device_types CLASS_MAPPER_BASE = { @@ -115,6 +118,7 @@ "dell_os10": DellOS10SSH, "dell_powerconnect": DellPowerConnectSSH, "dell_isilon": DellIsilonSSH, + "dlink_ds": DlinkDSSSH, "endace": EndaceSSH, "eltex": EltexSSH, "eltex_esr": EltexEsrSSH, @@ -168,6 +172,7 @@ "ubiquiti_edgeswitch": UbiquitiEdgeSSH, "vyatta_vyos": VyOSSSH, "vyos": VyOSSSH, + "watchguard_fireware": WatchguardFirewareSSH, } FILE_TRANSFER_MAP = { @@ -209,6 +214,7 @@ CLASS_MAPPER["cisco_xr_telnet"] = CiscoXrTelnet CLASS_MAPPER["dell_dnos6_telnet"] = DellDNOS6Telnet CLASS_MAPPER["dell_powerconnect_telnet"] = DellPowerConnectTelnet +CLASS_MAPPER["dlink_ds_telnet"] = DlinkDSTelnet CLASS_MAPPER["extreme_telnet"] = ExtremeExosTelnet CLASS_MAPPER["extreme_exos_telnet"] = ExtremeExosTelnet CLASS_MAPPER["extreme_netiron_telnet"] = ExtremeNetironTelnet diff --git a/tests/etc/commands.yml.example b/tests/etc/commands.yml.example index bceef7310..de0588987 100644 --- a/tests/etc/commands.yml.example +++ b/tests/etc/commands.yml.example @@ -282,6 +282,22 @@ keymile_nos: save_config_confirm: True save_config_response: '[OK]' +dlink_ds: + version: "show greeting_message" + basic: "show ipif" + config: + - enable command logging + config_verification: "show config current_config include \"logging\"" + extended_output: "show config current_config" # requires paging to be disabled + +dlink_ds_telnet: + version: "show greeting_message" + basic: "show ipif" + config: + - enable command logging + config_verification: "show config current_config include \"logging\"" + extended_output: "show config current_config" # requires paging to be disabled + ruijie_os: version: "show version" basic: "show ip interface brief" diff --git a/tests/etc/responses.yml.example b/tests/etc/responses.yml.example index 7d8b677ac..ba49ba9ae 100644 --- a/tests/etc/responses.yml.example +++ b/tests/etc/responses.yml.example @@ -223,6 +223,22 @@ keymile_nos: interface_ip: 1.2.3.5 version_banner: "NOS version 2.09 #0001" multiple_line_output: "Interface br328" + +dlink_ds: + base_prompt: DGS-3120-24TC:admin + router_prompt: DGS-3120-24TC:admin# + enable_prompt: DGS-3120-24TC:admin# + version_banner: "D-Link Corporation" + multiple_line_output: "End of configuration file" + interface_ip: 192.168.50.10 + +dlink_ds_telnet: + base_prompt: DGS-3120-24TC:admin + router_prompt: DGS-3120-24TC:admin# + enable_prompt: DGS-3120-24TC:admin# + version_banner: "D-Link Corporation" + multiple_line_output: "End of configuration file" + interface_ip: 192.168.50.10 ruijie_os: base_prompt: Ruijie diff --git a/tests/etc/test_devices.yml.example b/tests/etc/test_devices.yml.example index f71fe1a14..12c287fcb 100644 --- a/tests/etc/test_devices.yml.example +++ b/tests/etc/test_devices.yml.example @@ -178,6 +178,18 @@ keymile_nos: username: TEST password: TEST +dlink_ds: + device_type: dlink_ds + ip: 192.168.50.10 + username: admin + password: admin + +dlink_ds_telnet: + device_type: dlink_ds_telnet + ip: 192.168.50.10 + username: admin + password: admin + ruijie_os: device_type: ruijie_os ip: 1.1.1.1 From 49f7d97338c788ae73fb6a78384dca3ee8bf8c20 Mon Sep 17 00:00:00 2001 From: NGUYEN Duy Cuong Date: Fri, 27 Mar 2020 10:51:46 +0800 Subject: [PATCH 16/39] Fix the prompt pattern to include correct password change prompt or device prompt --- netmiko/huawei/huawei.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netmiko/huawei/huawei.py b/netmiko/huawei/huawei.py index c8d4ef201..39c02df56 100644 --- a/netmiko/huawei/huawei.py +++ b/netmiko/huawei/huawei.py @@ -111,8 +111,8 @@ class HuaweiSSH(HuaweiBase): def special_login_handler(self): """Handle password change request by ignoring it""" - password_change_prompt = r"(Change now|Please choose 'YES' or 'NO').+" - output = self.read_until_prompt_or_pattern(password_change_prompt) + password_change_prompt = r"((Change now|Please choose).+Y/N)|([\]>]\s*$)" + output = self.read_until_pattern(password_change_prompt) if re.search(password_change_prompt, output): self.write_channel("N\n") self.clear_buffer() From 774a5f9edf1d20f003e130f2577aba87d7057d57 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sat, 4 Apr 2020 08:50:30 -0700 Subject: [PATCH 17/39] Adding a comment --- netmiko/huawei/huawei.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netmiko/huawei/huawei.py b/netmiko/huawei/huawei.py index 39c02df56..adc8b1438 100644 --- a/netmiko/huawei/huawei.py +++ b/netmiko/huawei/huawei.py @@ -111,7 +111,8 @@ class HuaweiSSH(HuaweiBase): def special_login_handler(self): """Handle password change request by ignoring it""" - password_change_prompt = r"((Change now|Please choose).+Y/N)|([\]>]\s*$)" + # Huawei can prompt for password change. Search for that or for normal prompt + password_change_prompt = r"((Change now|Please choose))|([\]>]\s*$)" output = self.read_until_pattern(password_change_prompt) if re.search(password_change_prompt, output): self.write_channel("N\n") From 21a5bce4230be4c0fe30ce222841b3bc8ad2ebf4 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sat, 4 Apr 2020 09:01:27 -0700 Subject: [PATCH 18/39] Add new key for Huawei OLT --- PLATFORMS.md | 2 ++ netmiko/huawei/huawei_smartax.py | 2 ++ netmiko/ssh_dispatcher.py | 1 + 3 files changed, 5 insertions(+) diff --git a/PLATFORMS.md b/PLATFORMS.md index 806f0cb6d..3f2bea31f 100644 --- a/PLATFORMS.md +++ b/PLATFORMS.md @@ -29,6 +29,8 @@ - Extreme MLX/NetIron (Brocade/Foundry) - HPE Comware7 - Huawei +- Huawei OLT +- Huawei SmartAX - IP Infusion OcNOS - Juniper ScreenOS - Mellanox diff --git a/netmiko/huawei/huawei_smartax.py b/netmiko/huawei/huawei_smartax.py index 0b915bb38..2edebaf58 100644 --- a/netmiko/huawei/huawei_smartax.py +++ b/netmiko/huawei/huawei_smartax.py @@ -5,6 +5,8 @@ class HuaweiSmartAXSSH(CiscoBaseConnection): + """Supports Huawei SmartAX and OLT.""" + def session_preparation(self): """Prepare the session after the connection has been established.""" self.ansi_escape_codes = True diff --git a/netmiko/ssh_dispatcher.py b/netmiko/ssh_dispatcher.py index 3deafc622..8e51382cb 100644 --- a/netmiko/ssh_dispatcher.py +++ b/netmiko/ssh_dispatcher.py @@ -142,6 +142,7 @@ "hp_procurve": HPProcurveSSH, "huawei": HuaweiSSH, "huawei_smartax": HuaweiSmartAXSSH, + "huawei_olt": HuaweiSmartAXSSH, "huawei_vrpv8": HuaweiVrpv8SSH, "ipinfusion_ocnos": IpInfusionOcNOSSSH, "juniper": JuniperSSH, From fd67d8ff2741cfd3a2dd391a34fcca8c304cb87c Mon Sep 17 00:00:00 2001 From: Wim Van Deun <7521270+enzzzy@users.noreply.github.com> Date: Sat, 4 Apr 2020 20:46:43 +0200 Subject: [PATCH 19/39] Add _autodetect_remote_version Method to try auto-detect the device type bu matching a regular expression on the reported remove version of the SSH server. For example Cisco WLC's report the following version "'SSH-2.0-CISCO_WLC'". This allows us to detect a WLC by searching for "CISCO_WLC" in the reported remote version. Added "cisco_wlc" device type to SSH_MAPPER_BASE for autodetecting Cisco WLC's using the _autodetect_remote_version method. --- netmiko/ssh_autodetect.py | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/netmiko/ssh_autodetect.py b/netmiko/ssh_autodetect.py index 9b05d1e88..4d3bfeb07 100644 --- a/netmiko/ssh_autodetect.py +++ b/netmiko/ssh_autodetect.py @@ -167,6 +167,11 @@ "priority": 99, "dispatch": "_autodetect_std", }, + "cisco_wlc": { + "dispatch": "_autodetect_remote_version", + "search_patterns": [r"CISCO_WLC"], + "priority": 99, + }, } @@ -288,6 +293,42 @@ def _send_command_wrapper(self, cmd): else: return cached_results + def _autodetect_remote_version( + self, search_patterns=None, re_flags=re.I, priority=99 + ): + """ + Method to try auto-detect the device type, by matching a regular expression on the reported + remote version of the SSH server. + + Parameters + ---------- + search_patterns : list + A list of regular expression to look for in the reported remote SSH version + (default: None). + re_flags: re.flags, optional + Any flags from the python re module to modify the regular expression (default: re.I). + priority: int, optional + The confidence the match is right between 0 and 99 (default: 99). + """ + invalid_responses = [r"^$"] + + if not search_patterns: + return 0 + + try: + remote_version = self.connection.remote_conn.transport.remote_version + for pattern in invalid_responses: + match = re.search(pattern, remote_version, flags=re.I) + if match: + return 0 + for pattern in search_patterns: + match = re.search(pattern, remote_version, flags=re_flags) + if match: + return priority + except Exception: + return 0 + return 0 + def _autodetect_std(self, cmd="", search_patterns=None, re_flags=re.I, priority=99): """ Standard method to try to auto-detect the device type. This method will be called for each From 569beb763983b560ecc55209c1506129ba72c28d Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sun, 5 Apr 2020 10:58:01 -0700 Subject: [PATCH 20/39] Minor updates Unifi switch driver --- netmiko/ssh_dispatcher.py | 4 ++-- netmiko/ubiquiti/__init__.py | 4 ++-- ...unifi_switch_ssh.py => unifiswitch_ssh.py} | 23 +++++++++++++++---- 3 files changed, 22 insertions(+), 9 deletions(-) rename netmiko/ubiquiti/{unifi_switch_ssh.py => unifiswitch_ssh.py} (54%) diff --git a/netmiko/ssh_dispatcher.py b/netmiko/ssh_dispatcher.py index bc329e416..4df77a046 100644 --- a/netmiko/ssh_dispatcher.py +++ b/netmiko/ssh_dispatcher.py @@ -78,7 +78,7 @@ from netmiko.terminal_server import TerminalServerSSH from netmiko.terminal_server import TerminalServerTelnet from netmiko.ubiquiti import UbiquitiEdgeSSH -from netmiko.ubiquiti import UnifiSwitchSSH +from netmiko.ubiquiti import UbiquitiUnifiSwitchSSH from netmiko.vyos import VyOSSSH from netmiko.watchguard import WatchguardFirewareSSH @@ -172,7 +172,7 @@ "sophos_sfos": SophosSfosSSH, "ubiquiti_edge": UbiquitiEdgeSSH, "ubiquiti_edgeswitch": UbiquitiEdgeSSH, - "ubiquiti_unifi": UnifiSwitchSSH, + "ubiquiti_unifiswitch": UbiquitiUnifiSwitchSSH, "vyatta_vyos": VyOSSSH, "vyos": VyOSSSH, "watchguard_fireware": WatchguardFirewareSSH, diff --git a/netmiko/ubiquiti/__init__.py b/netmiko/ubiquiti/__init__.py index 5b17570df..214673edf 100644 --- a/netmiko/ubiquiti/__init__.py +++ b/netmiko/ubiquiti/__init__.py @@ -1,4 +1,4 @@ from netmiko.ubiquiti.edge_ssh import UbiquitiEdgeSSH -from netmiko.ubiquiti.unifi_switch_ssh import UnifiSwitchSSH +from netmiko.ubiquiti.unifiswitch_ssh import UbiquitiUnifiSwitchSSH -__all__ = ["UbiquitiEdgeSSH", "UnifiSwitchSSH"] +__all__ = ["UbiquitiEdgeSSH", "UnifiSwitchSSH", "UbiquitiUnifiSwitchSSH"] diff --git a/netmiko/ubiquiti/unifi_switch_ssh.py b/netmiko/ubiquiti/unifiswitch_ssh.py similarity index 54% rename from netmiko/ubiquiti/unifi_switch_ssh.py rename to netmiko/ubiquiti/unifiswitch_ssh.py index 5a585b8db..a07021c7e 100644 --- a/netmiko/ubiquiti/unifi_switch_ssh.py +++ b/netmiko/ubiquiti/unifiswitch_ssh.py @@ -2,13 +2,15 @@ from netmiko.ubiquiti.edge_ssh import UbiquitiEdgeSSH -class UnifiSwitchSSH(UbiquitiEdgeSSH): +class UbiquitiUnifiSwitchSSH(UbiquitiEdgeSSH): def session_preparation(self): - """Prepare the session after the connection has been established. + """ + Prepare the session after the connection has been established. When SSHing to a UniFi switch, the session initially starts at a Linux shell. Nothing interesting can be done in this environment, however, running `telnet localhost` drops the session to a more familiar - environment.""" + environment. + """ self._test_channel_read() self.set_base_prompt() @@ -23,5 +25,16 @@ def session_preparation(self): time.sleep(0.3 * self.global_delay_factor) self.clear_buffer() - def disable_paging(self, command="terminal length 0"): - super(UbiquitiEdgeSSH, self).disable_paging(command=command) + def cleanup(self, command="exit"): + """Gracefully exit the SSH session.""" + try: + # The pattern="" forces use of send_command_timing + if self.check_config_mode(pattern=""): + self.exit_config_mode() + + # Exit from the first 'telnet localhost' + self.write_channel(command + self.RETURN) + except Exception: + pass + + super().cleanup() From ca29aa5a6b6f0baf4794f8091299831253ce238d Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sun, 5 Apr 2020 11:10:43 -0700 Subject: [PATCH 21/39] Updates to example files --- tests/etc/commands.yml.example | 2 +- tests/etc/responses.yml.example | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/etc/commands.yml.example b/tests/etc/commands.yml.example index 7bb15707b..b58697cd1 100644 --- a/tests/etc/commands.yml.example +++ b/tests/etc/commands.yml.example @@ -137,7 +137,7 @@ ubiquiti_edge: - "logging persistent 4" config_verification: "show running-config | include 'logging'" -ubiquiti_unifi: +ubiquiti_unifiswitch: version: "show version" basic: "show network" extended_output: "show running-config" diff --git a/tests/etc/responses.yml.example b/tests/etc/responses.yml.example index 2aaf5d46f..886b28e91 100644 --- a/tests/etc/responses.yml.example +++ b/tests/etc/responses.yml.example @@ -89,7 +89,7 @@ ubiquiti_edge: cmd_response_init: "" cmd_response_final: "logging persistent 4" -ubiquiti_unifi: +ubiquiti_unifiswitch: base_prompt: "(UBNT) " router_prompt: "(UBNT) >" enable_prompt: "(UBNT) #" From f622508eca223f434db7307fb1f627824e87d37d Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Sun, 5 Apr 2020 11:24:29 -0700 Subject: [PATCH 22/39] Update platforms file --- PLATFORMS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/PLATFORMS.md b/PLATFORMS.md index 3f2bea31f..859a327b3 100644 --- a/PLATFORMS.md +++ b/PLATFORMS.md @@ -73,5 +73,6 @@ - QuantaMesh - Rad ETX - Sophos SFOS +- Ubiquiti Unifi Switch - Versa Networks FlexVNF - Watchguard Firebox From f99a37e3921b440fbf22d8f6919266a3595b4b70 Mon Sep 17 00:00:00 2001 From: Fikirsiz Date: Mon, 6 Apr 2020 12:06:14 +0300 Subject: [PATCH 23/39] Added alias for huawei_olt_telnet --- netmiko/ssh_dispatcher.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netmiko/ssh_dispatcher.py b/netmiko/ssh_dispatcher.py index 4df77a046..ccbdf7811 100644 --- a/netmiko/ssh_dispatcher.py +++ b/netmiko/ssh_dispatcher.py @@ -225,6 +225,7 @@ CLASS_MAPPER["hp_procurve_telnet"] = HPProcurveTelnet CLASS_MAPPER["hp_comware_telnet"] = HPComwareTelnet CLASS_MAPPER["huawei_telnet"] = HuaweiTelnet +CLASS_MAPPER["huawei_olt_telnet"] = HuaweiSmartAXSSH CLASS_MAPPER["ipinfusion_ocnos_telnet"] = IpInfusionOcNOSTelnet CLASS_MAPPER["juniper_junos_telnet"] = JuniperTelnet CLASS_MAPPER["paloalto_panos_telnet"] = PaloAltoPanosTelnet From 3000ce33107ff3498600d40e842f4eee77b96edb Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Thu, 16 Apr 2020 14:02:15 -0700 Subject: [PATCH 24/39] Fixing global_cmd_verify bug and aruba issue with config mode --- netmiko/aruba/aruba_ssh.py | 6 ++++++ netmiko/base_connection.py | 20 ++++++++++++++++---- netmiko/huawei/huawei_smartax.py | 5 ++++- netmiko/nokia/nokia_sros_ssh.py | 15 +++++++++++---- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/netmiko/aruba/aruba_ssh.py b/netmiko/aruba/aruba_ssh.py index 4cf9b3dcf..34c4dccd8 100644 --- a/netmiko/aruba/aruba_ssh.py +++ b/netmiko/aruba/aruba_ssh.py @@ -33,3 +33,9 @@ def check_config_mode(self, check_string="(config) #", pattern=""): if not pattern: pattern = re.escape(self.base_prompt[:16]) return super().check_config_mode(check_string=check_string, pattern=pattern) + + def config_mode(self, config_command="configure term", pattern=""): + """ + Aruba auto completes on space so 'configure' needs fully spelled-out. + """ + return super().config_mode(config_command=config_command, pattern=pattern) diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index fc295e594..c802658f3 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -1028,7 +1028,10 @@ def disable_paging(self, command="terminal length 0", delay_factor=1): log.debug(f"Command: {command}") self.write_channel(command) # Make sure you read until you detect the command echo (avoid getting out of sync) - output = self.read_until_pattern(pattern=re.escape(command.strip())) + if self.global_cmd_verify: + output = self.read_until_pattern(pattern=re.escape(command.strip())) + else: + output = self.read_until_prompt() log.debug(f"{output}") log.debug("Exiting disable_paging") return output @@ -1051,7 +1054,10 @@ def set_terminal_width(self, command="", delay_factor=1): command = self.normalize_cmd(command) self.write_channel(command) # Make sure you read until you detect the command echo (avoid getting out of sync) - output = self.read_until_pattern(pattern=re.escape(command.strip())) + if self.global_cmd_verify: + output = self.read_until_pattern(pattern=re.escape(command.strip())) + else: + output = self.read_until_prompt() return output def set_base_prompt( @@ -1618,7 +1624,10 @@ def config_mode(self, config_command="", pattern=""): if not self.check_config_mode(): self.write_channel(self.normalize_cmd(config_command)) # Make sure you read until you detect the command echo (avoid getting out of sync) - output += self.read_until_pattern(pattern=re.escape(config_command.strip())) + if self.global_cmd_verify: + output += self.read_until_pattern( + pattern=re.escape(config_command.strip()) + ) if not re.search(pattern, output, flags=re.M): output += self.read_until_pattern(pattern=pattern) if not self.check_config_mode(): @@ -1638,7 +1647,10 @@ def exit_config_mode(self, exit_config="", pattern=""): if self.check_config_mode(): self.write_channel(self.normalize_cmd(exit_config)) # Make sure you read until you detect the command echo (avoid getting out of sync) - output += self.read_until_pattern(pattern=re.escape(exit_config.strip())) + if self.global_cmd_verify: + output += self.read_until_pattern( + pattern=re.escape(exit_config.strip()) + ) if not re.search(pattern, output, flags=re.M): output += self.read_until_pattern(pattern=pattern) if self.check_config_mode(): diff --git a/netmiko/huawei/huawei_smartax.py b/netmiko/huawei/huawei_smartax.py index 2edebaf58..97059b2ec 100644 --- a/netmiko/huawei/huawei_smartax.py +++ b/netmiko/huawei/huawei_smartax.py @@ -43,7 +43,10 @@ def _disable_smart_interaction(self, command="undo smart", delay_factor=1): log.debug("In disable_smart_interaction") log.debug(f"Command: {command}") self.write_channel(command) - output = self.read_until_pattern(pattern=re.escape(command.strip())) + if self.global_cmd_verify: + output = self.read_until_pattern(pattern=re.escape(command.strip())) + else: + output = self.read_until_prompt() log.debug(f"{output}") log.debug("Exiting disable_smart_interaction") diff --git a/netmiko/nokia/nokia_sros_ssh.py b/netmiko/nokia/nokia_sros_ssh.py index 35f49bf1f..f3f51d48d 100644 --- a/netmiko/nokia/nokia_sros_ssh.py +++ b/netmiko/nokia/nokia_sros_ssh.py @@ -89,7 +89,8 @@ def exit_config_mode(self, *args, **kwargs): output += self._discard() cmd = "quit-config" self.write_channel(self.normalize_cmd(cmd)) - output += self.read_until_pattern(pattern=re.escape(cmd)) + if self.global_cmd_verify: + output += self.read_until_pattern(pattern=re.escape(cmd)) if self.check_config_mode(): raise ValueError("Failed to exit configuration mode") return output @@ -124,16 +125,20 @@ def commit(self, *args, **kwargs): log.info("Apply uncommitted changes!") cmd = "commit" self.write_channel(self.normalize_cmd(cmd)) - output += self.read_until_pattern(pattern=re.escape(cmd)) + if self.global_cmd_verify: + output += self.read_until_pattern(pattern=re.escape(cmd)) output += self.read_until_pattern(r"@") return output def _exit_all(self): """Return to the 'root' context.""" + output = "" exit_cmd = "exit all" self.write_channel(self.normalize_cmd(exit_cmd)) # Make sure you read until you detect the command echo (avoid getting out of sync) - return self.read_until_pattern(pattern=re.escape(exit_cmd)) + if self.global_cmd_verify: + output += self.read_until_pattern(pattern=re.escape(exit_cmd)) + return output def _discard(self): """Discard changes from private candidate for Nokia SR OS""" @@ -141,7 +146,9 @@ def _discard(self): if "@" in self.base_prompt: cmd = "discard" self.write_channel(self.normalize_cmd(cmd)) - new_output = self.read_until_pattern(pattern=re.escape(cmd)) + new_output = "" + if self.global_cmd_verify: + new_output = self.read_until_pattern(pattern=re.escape(cmd)) if "@" not in new_output: new_output += self.read_until_prompt() output += new_output From 305d0973a740618d3761e39ba8147f50aa3e42d2 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Thu, 16 Apr 2020 18:24:02 -0700 Subject: [PATCH 25/39] Set global_cmd_verify = False for Aruba --- netmiko/aruba/aruba_ssh.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/netmiko/aruba/aruba_ssh.py b/netmiko/aruba/aruba_ssh.py index 34c4dccd8..3338ccd0a 100644 --- a/netmiko/aruba/aruba_ssh.py +++ b/netmiko/aruba/aruba_ssh.py @@ -10,6 +10,9 @@ class ArubaSSH(CiscoSSHConnection): def __init__(self, **kwargs): if kwargs.get("default_enter") is None: kwargs["default_enter"] = "\r" + # Aruba has an auto-complete on space behavior that is problematic + if kwargs.get("global_cmd_verify") is None: + kwargs["global_cmd_verify"] = False return super().__init__(**kwargs) def session_preparation(self): From c205dce6f4aaf10ac68023f705528fc745a48c76 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Thu, 16 Apr 2020 13:09:12 -0700 Subject: [PATCH 26/39] Improving terminal width and testing --- netmiko/base_connection.py | 17 ++++++----------- tests/test_netmiko_show.py | 9 +++++++++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index c802658f3..892299227 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -870,18 +870,16 @@ def _sanitize_output( output = self.strip_prompt(output) return output - def establish_connection(self, width=None, height=None): + def establish_connection(self, width=511, height=1000): """Establish SSH connection to the network device Timeout will generate a NetmikoTimeoutException Authentication failure will generate a NetmikoAuthenticationException - width and height are needed for Fortinet paging setting. - - :param width: Specified width of the VT100 terminal window + :param width: Specified width of the VT100 terminal window (default: 511) :type width: int - :param height: Specified height of the VT100 terminal window + :param height: Specified height of the VT100 terminal window (default: 1000) :type height: int """ if self.protocol == "telnet": @@ -917,12 +915,9 @@ def establish_connection(self, width=None, height=None): print(f"SSH connection established to {self.host}:{self.port}") # Use invoke_shell to establish an 'interactive session' - if width and height: - self.remote_conn = self.remote_conn_pre.invoke_shell( - term="vt100", width=width, height=height - ) - else: - self.remote_conn = self.remote_conn_pre.invoke_shell() + self.remote_conn = self.remote_conn_pre.invoke_shell( + term="vt100", width=width, height=height + ) self.remote_conn.settimeout(self.blocking_timeout) if self.keepalive: diff --git a/tests/test_netmiko_show.py b/tests/test_netmiko_show.py index 44b3e3dfd..a4ee92771 100755 --- a/tests/test_netmiko_show.py +++ b/tests/test_netmiko_show.py @@ -35,6 +35,15 @@ def test_disable_paging(net_connect, commands, expected_responses): assert expected_responses["multiple_line_output"] in multiple_line_output +def test_terminal_width(net_connect, commands, expected_responses): + """Verify long commands work properly.""" + import ipdb; ipdb.set_trace() + wide_command = commands.get("wide_command") + if wide_command: + net_connect.send_command(wide_command) + assert True + + def test_ssh_connect(net_connect, commands, expected_responses): """Verify the connection was established successfully.""" show_version = net_connect.send_command(commands["version"]) From 6dd9d104f4b4e8355f8b041187c00b51e7b63252 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Thu, 16 Apr 2020 20:31:22 -0700 Subject: [PATCH 27/39] Disable global_cmd_verify for ASA if terminal width can't be set --- netmiko/cisco/cisco_asa_ssh.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/netmiko/cisco/cisco_asa_ssh.py b/netmiko/cisco/cisco_asa_ssh.py index ac9f92bd2..5aece81ca 100644 --- a/netmiko/cisco/cisco_asa_ssh.py +++ b/netmiko/cisco/cisco_asa_ssh.py @@ -22,6 +22,9 @@ def session_preparation(self): except ValueError: # Don't fail for the terminal width pass + else: + # Disable cmd_verify if the terminal width can't be set + self.global_cmd_verify = False # Clear the read buffer time.sleep(0.3 * self.global_delay_factor) From 304d625d127204a332f8f9f3e6697cd104348b1f Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Thu, 16 Apr 2020 21:06:14 -0700 Subject: [PATCH 28/39] Updating commands.yml --- tests/etc/commands.yml.example | 112 ++++++++++++++++++++++++++++++--- 1 file changed, 103 insertions(+), 9 deletions(-) diff --git a/tests/etc/commands.yml.example b/tests/etc/commands.yml.example index b58697cd1..bb254cab1 100644 --- a/tests/etc/commands.yml.example +++ b/tests/etc/commands.yml.example @@ -3,20 +3,61 @@ cisco_ios: version: "show version" basic: "show ip interface brief" + wide_command: "show ip access-lists myverybiglongaccesslistthatdoesntexistandwherethisexceeds80characterssolinewrappingoccurs" extended_output: "show version" # requires paging to be disabled - config: + config: - "logging buffered 20000" # base command - "no logging console" - "logging buffered 20010" # something you can verify has changed config_verification: "show run | inc logging buffer" - config_file: 'cisco_ios_commands.txt' - save_config_cmd: 'copy run start' + config_file: "cisco_ios_commands.txt" + save_config_confirm: False + +cisco_ios_telnet: + version: "show version" + basic: "show ip interface brief" + wide_command: "show ip access-lists myverybiglongaccesslistthatdoesntexistandwherethisexceeds80characterssolinewrappingoccurs" + extended_output: "show version" # requires paging to be disabled + config: + - "logging buffered 20000" # base command + - "no logging console" + - "logging buffered 20010" # something you can verify has changed + config_verification: "show run | inc logging buffer" + config_file: "cisco_ios_commands.txt" + save_config_confirm: False + +cisco_xr: + version: "show version" + basic: "show ip interface brief" + basic_textfsm: "show interface brief" + wide_command: "show access-list myverybiglongaccesslistthatdoesntexistandwherethisexceeds80characterssolinewrappingoccurs" + extended_output: "show version" # requires paging to be disabled + config: + - "logging buffered 4000000" # base command + - "no logging console" + - "logging buffered 4000010" # something you can verify has changed + config_verification: "show run | inc logging buffer" + support_commit: True + commit_verification: "show configuration commit list 1 detail" + +cisco_s300: + version: "show version" + basic: "show ip interface" + wide_command: "show ip access-lists myverybiglongaccesslistthatdoesntexistandwherethisexceeds80characterssolinewrappingoccurs" + extended_output: "show run" # requires paging to be disabled + config: + - 'logging buffered notifications' + - 'no logging console' + - 'logging buffered warnings' + config_verification: "show run" + config_file: "cisco_ios_commands.txt" save_config_confirm: True - save_config_response: '' cisco_asa: version: "show version" basic: "show ip" + wide_command: "show access-list myverybiglongaccesslistthatdoesntexistandwherethisexceeds80characterssolinewrappingoccurs" + basic_textfsm: "show route" extended_output: "show version" # requires paging to be disabled config: - 'logging buffered notifications' @@ -28,8 +69,9 @@ cisco_asa: arista_eos: version: "show version" basic: "show ip int brief" + wide_command: "show ip access-lists myverybiglongaccesslistthatdoesntexistandwherethisexceeds80characterssolinewrappingoccurs" extended_output: "show logging" # requires paging to be disabled - config: + config: - "logging buffered 20000" - "no logging console" - "logging buffered 20010" @@ -38,18 +80,30 @@ arista_eos: hp_procurve: version: "show version" basic: "show ip" + basic_textfsm: "show system" extended_output: "show logging" # requires paging to be disabled - config: + config: - 'time timezone -420' - 'time daylight-time-rule Continental-US-and-Canada' - 'time timezone -480' - config_verification: "show run" + config_verification: "show run" -juniper: +hp_comware: + version: "display version" + basic: "display ip int brief" + extended_output: "display version" # requires paging to be disabled + config: + - 'ip host test1 1.1.1.1' + - 'undo ip host test1 1.1.1.1' + - 'ip host test1 1.1.1.2' + config_verification: "display current-configuration" + +juniper_junos: version: "show version" basic: "show interfaces terse" + basic_textfsm: "show interfaces" extended_output: "show configuration" # requires paging to be disabled - config: + config: - 'set system syslog archive size 110k files 3' - 'set system time-zone America/New_York' - 'set system syslog archive size 120k files 3' @@ -58,6 +112,46 @@ juniper: rollback: 'rollback 0' commit_verification: "run show system commit" +linux: + version: "uname -a" + basic: "ifconfig -a | grep inet | grep -v inet6 " + wide_command: 'echo "cable modem deny 0015.f2fe.ba11"; echo "cable modem deny 0015.f2fe.ba12"; echo "cable modem deny 0015.f2fe.ba13"' + extended_output: "netstat -an" # requires paging to be disabled + # config_long_command: "ls verylongcommandnamethatwillcauselinewrapissuesasitdoesnotfitonesinglescreengreaterthan127charsandthensomesothingsreallyarennotright" + # config_verification: "ls" + +dell_force10: + version: "show version" + basic: "show ip interface brief managementethernet 0/0" + extended_output: "show ip interface brief" # requires paging to be disabled + config: + - "logging buffered 50000" # base command + - "no logging console" + - "logging buffered 50010" # something you can verify has changed + config_verification: "show run" + +cisco_nxos: + version: "show version" + basic: "show ip interface brief vrf management" + basic_textfsm: "show interface brief" + extended_output: "show logging" # requires paging to be disabled + config: + - "logging console 0" # base command + - "no logging console" + - "logging console 4" # something you can verify has changed + config_verification: "show run | inc logging" + config_long_command: "snmp-server location verylongsnmplocationnamethatwillcauselinewrapissuesasitdoesnotfitonesinglescreengreaterthan127charsandthensomesothingsreallyarennotright" + +cisco_xe: + version: "show version" + basic: "show ip interface brief" + extended_output: "show version" # requires paging to be disabled + config: + - "logging buffered 20000" # base command + - "no logging console" + - "logging buffered 20010" # something you can verify has changed + config_verification: "show run | inc logging buffer" + juniper_screenos: version: "get system version" basic: "get route" From 944eaef083df780daf1a34d3b86817c4f1435dc0 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Thu, 16 Apr 2020 21:08:13 -0700 Subject: [PATCH 29/39] Removed Pdb --- tests/test_netmiko_show.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_netmiko_show.py b/tests/test_netmiko_show.py index a4ee92771..24309a08e 100755 --- a/tests/test_netmiko_show.py +++ b/tests/test_netmiko_show.py @@ -37,7 +37,6 @@ def test_disable_paging(net_connect, commands, expected_responses): def test_terminal_width(net_connect, commands, expected_responses): """Verify long commands work properly.""" - import ipdb; ipdb.set_trace() wide_command = commands.get("wide_command") if wide_command: net_connect.send_command(wide_command) From e0681bcee248e409dcec9f0918a8cd8101cb1c0d Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Thu, 16 Apr 2020 21:14:22 -0700 Subject: [PATCH 30/39] Set terminal width for Vyatta Driver --- netmiko/vyos/vyos_ssh.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netmiko/vyos/vyos_ssh.py b/netmiko/vyos/vyos_ssh.py index 1efb031ea..c05a75c51 100644 --- a/netmiko/vyos/vyos_ssh.py +++ b/netmiko/vyos/vyos_ssh.py @@ -10,6 +10,7 @@ def session_preparation(self): self._test_channel_read() self.set_base_prompt() self.disable_paging(command="set terminal length 0") + self.set_terminal_width(command="set terminal width 512") # Clear the read buffer time.sleep(0.3 * self.global_delay_factor) self.clear_buffer() From 146f4bee95d494f4fef2d809cd0a866b6000fbaf Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Fri, 17 Apr 2020 09:31:17 -0700 Subject: [PATCH 31/39] Improve commit behavior on Nokia SR-OS (#1672) --- netmiko/base_connection.py | 8 ++++---- netmiko/huawei/huawei_smartax.py | 2 +- netmiko/nokia/nokia_sros_ssh.py | 21 ++++++++++++++------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index 892299227..185038cbf 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -1023,7 +1023,7 @@ def disable_paging(self, command="terminal length 0", delay_factor=1): log.debug(f"Command: {command}") self.write_channel(command) # Make sure you read until you detect the command echo (avoid getting out of sync) - if self.global_cmd_verify: + if self.global_cmd_verify is not False: output = self.read_until_pattern(pattern=re.escape(command.strip())) else: output = self.read_until_prompt() @@ -1049,7 +1049,7 @@ def set_terminal_width(self, command="", delay_factor=1): command = self.normalize_cmd(command) self.write_channel(command) # Make sure you read until you detect the command echo (avoid getting out of sync) - if self.global_cmd_verify: + if self.global_cmd_verify is not False: output = self.read_until_pattern(pattern=re.escape(command.strip())) else: output = self.read_until_prompt() @@ -1619,7 +1619,7 @@ def config_mode(self, config_command="", pattern=""): if not self.check_config_mode(): self.write_channel(self.normalize_cmd(config_command)) # Make sure you read until you detect the command echo (avoid getting out of sync) - if self.global_cmd_verify: + if self.global_cmd_verify is not False: output += self.read_until_pattern( pattern=re.escape(config_command.strip()) ) @@ -1642,7 +1642,7 @@ def exit_config_mode(self, exit_config="", pattern=""): if self.check_config_mode(): self.write_channel(self.normalize_cmd(exit_config)) # Make sure you read until you detect the command echo (avoid getting out of sync) - if self.global_cmd_verify: + if self.global_cmd_verify is not False: output += self.read_until_pattern( pattern=re.escape(exit_config.strip()) ) diff --git a/netmiko/huawei/huawei_smartax.py b/netmiko/huawei/huawei_smartax.py index 97059b2ec..6fd3e807e 100644 --- a/netmiko/huawei/huawei_smartax.py +++ b/netmiko/huawei/huawei_smartax.py @@ -43,7 +43,7 @@ def _disable_smart_interaction(self, command="undo smart", delay_factor=1): log.debug("In disable_smart_interaction") log.debug(f"Command: {command}") self.write_channel(command) - if self.global_cmd_verify: + if self.global_cmd_verify is not False: output = self.read_until_pattern(pattern=re.escape(command.strip())) else: output = self.read_until_prompt() diff --git a/netmiko/nokia/nokia_sros_ssh.py b/netmiko/nokia/nokia_sros_ssh.py index f3f51d48d..f94afba5a 100644 --- a/netmiko/nokia/nokia_sros_ssh.py +++ b/netmiko/nokia/nokia_sros_ssh.py @@ -89,8 +89,10 @@ def exit_config_mode(self, *args, **kwargs): output += self._discard() cmd = "quit-config" self.write_channel(self.normalize_cmd(cmd)) - if self.global_cmd_verify: + if self.global_cmd_verify is not False: output += self.read_until_pattern(pattern=re.escape(cmd)) + else: + output += self.read_until_prompt() if self.check_config_mode(): raise ValueError("Failed to exit configuration mode") return output @@ -125,9 +127,12 @@ def commit(self, *args, **kwargs): log.info("Apply uncommitted changes!") cmd = "commit" self.write_channel(self.normalize_cmd(cmd)) - if self.global_cmd_verify: - output += self.read_until_pattern(pattern=re.escape(cmd)) - output += self.read_until_pattern(r"@") + new_output = "" + if self.global_cmd_verify is not False: + new_output += self.read_until_pattern(pattern=re.escape(cmd)) + if "@" not in new_output: + new_output += self.read_until_pattern(r"@") + output += new_output return output def _exit_all(self): @@ -136,8 +141,10 @@ def _exit_all(self): exit_cmd = "exit all" self.write_channel(self.normalize_cmd(exit_cmd)) # Make sure you read until you detect the command echo (avoid getting out of sync) - if self.global_cmd_verify: + if self.global_cmd_verify is not False: output += self.read_until_pattern(pattern=re.escape(exit_cmd)) + else: + output += self.read_until_prompt() return output def _discard(self): @@ -147,8 +154,8 @@ def _discard(self): cmd = "discard" self.write_channel(self.normalize_cmd(cmd)) new_output = "" - if self.global_cmd_verify: - new_output = self.read_until_pattern(pattern=re.escape(cmd)) + if self.global_cmd_verify is not False: + new_output += self.read_until_pattern(pattern=re.escape(cmd)) if "@" not in new_output: new_output += self.read_until_prompt() output += new_output From 3533b630b1e55dfabc3744c32043eedb0a326eba Mon Sep 17 00:00:00 2001 From: Adrian Lasota <64520076+adrianlasota@users.noreply.github.com> Date: Wed, 29 Apr 2020 17:05:09 +0200 Subject: [PATCH 32/39] Added Dell OS9, Dell PowerConnect and Mellanox MLNXOS to ssh autodetection (#1702) --- netmiko/ssh_autodetect.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/netmiko/ssh_autodetect.py b/netmiko/ssh_autodetect.py index 4d3bfeb07..ca46550c5 100644 --- a/netmiko/ssh_autodetect.py +++ b/netmiko/ssh_autodetect.py @@ -105,12 +105,27 @@ "priority": 99, "dispatch": "_autodetect_std", }, + "dell_os9": { + "cmd": "show system", + "search_patterns": [ + r"Dell Application Software Version: 9", + r"Dell Networking OS Version : 9", + ], + "priority": 99, + "dispatch": "_autodetect_std", + }, "dell_os10": { "cmd": "show version", "search_patterns": [r"Dell EMC Networking OS10-Enterprise"], "priority": 99, "dispatch": "_autodetect_std", }, + "dell_powerconnect": { + "cmd": "show system", + "search_patterns": [r"PowerConnect"], + "priority": 99, + "dispatch": "_autodetect_std", + }, "f5_tmsh": { "cmd": "show sys version", "search_patterns": [r"BIG-IP"], @@ -172,6 +187,12 @@ "search_patterns": [r"CISCO_WLC"], "priority": 99, }, + "mellanox_mlnxos": { + "cmd": "show version", + "search_patterns": [r"Onyx", r"SX_PPC_M460EX"], + "priority": 99, + "dispatch": "_autodetect_std", + }, } From 063bdd4184bbb08fad5f82f2b429ab1323155fc3 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Thu, 30 Apr 2020 21:15:52 +0200 Subject: [PATCH 33/39] added vscode .devcontainer to ignore list (#1707) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b8d9663f4..ebda2d043 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ bin/.netmiko.cfg .vscode/* .idea/ .venv/ +.devcontainer # Sphinx documentation docs/build/doctrees/ From 211fd9da18b49acd65f390f722a460b55bc672e2 Mon Sep 17 00:00:00 2001 From: Brett Lykins Date: Sat, 2 May 2020 11:18:05 -0500 Subject: [PATCH 34/39] adding additional venv files to .gitignore (#1695) --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index ebda2d043..c2be9af79 100644 --- a/.gitignore +++ b/.gitignore @@ -15,9 +15,16 @@ dist netmiko.egg-info .cache bin/.netmiko.cfg + +# IDE Related files .vscode/* .idea/ + +# Virtual Environment files .venv/ +bin/ +lib/ +pyvenv.cfg .devcontainer # Sphinx documentation From ccc6050b502b6db39306565f69386929869ef264 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Wed, 6 May 2020 17:07:53 +0200 Subject: [PATCH 35/39] increase file size for scp_get (#1720) --- tests/test_netmiko_scp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_netmiko_scp.py b/tests/test_netmiko_scp.py index 9852a1037..b480f4a01 100755 --- a/tests/test_netmiko_scp.py +++ b/tests/test_netmiko_scp.py @@ -70,7 +70,7 @@ def test_verify_space_available_get(scp_fixture_get): ssh_conn, scp_transfer = scp_fixture_get assert scp_transfer.verify_space_available() is True # intentional make there not be enough space available - scp_transfer.file_size = 100000000000000 + scp_transfer.file_size = 100000000000000000 assert scp_transfer.verify_space_available() is False From f7b8ac72878dd97570e4b1c7531867a9404282b1 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Wed, 6 May 2020 14:42:00 -0700 Subject: [PATCH 36/39] Add SR-OS SCP support; Convert file_transfer to more generic verification (#1719) --- netmiko/nokia/__init__.py | 4 +- netmiko/nokia/nokia_sros_ssh.py | 102 ++++++++++++++++++++++++++++++++ netmiko/scp_functions.py | 19 +++--- netmiko/scp_handler.py | 7 ++- netmiko/ssh_dispatcher.py | 3 +- tests/conftest.py | 19 ++++++ tests/test_netmiko_scp.py | 15 ++--- 7 files changed, 146 insertions(+), 23 deletions(-) mode change 100644 => 100755 netmiko/nokia/__init__.py mode change 100644 => 100755 netmiko/nokia/nokia_sros_ssh.py mode change 100644 => 100755 netmiko/ssh_dispatcher.py diff --git a/netmiko/nokia/__init__.py b/netmiko/nokia/__init__.py old mode 100644 new mode 100755 index 87cabb5a3..c921cc047 --- a/netmiko/nokia/__init__.py +++ b/netmiko/nokia/__init__.py @@ -1,3 +1,3 @@ -from netmiko.nokia.nokia_sros_ssh import NokiaSrosSSH +from netmiko.nokia.nokia_sros_ssh import NokiaSrosSSH, NokiaSrosFileTransfer -__all__ = ["NokiaSrosSSH"] +__all__ = ["NokiaSrosSSH", "NokiaSrosFileTransfer"] diff --git a/netmiko/nokia/nokia_sros_ssh.py b/netmiko/nokia/nokia_sros_ssh.py old mode 100644 new mode 100755 index f94afba5a..97fbe544a --- a/netmiko/nokia/nokia_sros_ssh.py +++ b/netmiko/nokia/nokia_sros_ssh.py @@ -7,10 +7,12 @@ # https://github.com/ktbyers/netmiko/blob/develop/LICENSE import re +import os import time from netmiko import log from netmiko.base_connection import BaseConnection +from netmiko.scp_handler import BaseFileTransfer class NokiaSrosSSH(BaseConnection): @@ -39,6 +41,8 @@ def session_preparation(self): # "@" indicates model-driven CLI (vs Classical CLI) if "@" in self.base_prompt: self.disable_paging(command="environment more false") + # To perform file operations we need to disable paging in classical-CLI also + self.disable_paging(command="//environment no more") self.set_terminal_width(command="environment console width 512") else: self.disable_paging(command="environment no more") @@ -182,3 +186,101 @@ def cleanup(self, command="logout"): # Always try to send final 'logout'. self._session_log_fin = True self.write_channel(command + self.RETURN) + + +class NokiaSrosFileTransfer(BaseFileTransfer): + def __init__( + self, ssh_conn, source_file, dest_file, hash_supported=False, **kwargs + ): + super().__init__( + ssh_conn, source_file, dest_file, hash_supported=hash_supported, **kwargs + ) + + def _file_cmd_prefix(self): + """ + Allow MD-CLI to execute file operations by using classical CLI. + + Returns "//" if the current prompt is MD-CLI (empty string otherwise). + """ + return "//" if "@" in self.ssh_ctl_chan.base_prompt else "" + + def remote_space_available(self, search_pattern=r"(\d+)\s+\w+\s+free"): + """Return space available on remote device.""" + + # Sample text for search_pattern. + # " 3 Dir(s) 961531904 bytes free." + remote_cmd = self._file_cmd_prefix() + "file dir {}".format(self.file_system) + remote_output = self.ssh_ctl_chan.send_command(remote_cmd) + match = re.search(search_pattern, remote_output) + return int(match.group(1)) + + def check_file_exists(self, remote_cmd=""): + """Check if destination file exists (returns boolean).""" + + if self.direction == "put": + if not remote_cmd: + remote_cmd = self._file_cmd_prefix() + "file dir {}/{}".format( + self.file_system, self.dest_file + ) + remote_out = self.ssh_ctl_chan.send_command(remote_cmd) + if "File Not Found" in remote_out: + return False + elif self.dest_file in remote_out: + return True + else: + raise ValueError("Unexpected output from check_file_exists") + elif self.direction == "get": + return os.path.exists(self.dest_file) + + def remote_file_size(self, remote_cmd=None, remote_file=None): + """Get the file size of the remote file.""" + + if remote_file is None: + if self.direction == "put": + remote_file = self.dest_file + elif self.direction == "get": + remote_file = self.source_file + if not remote_cmd: + remote_cmd = self._file_cmd_prefix() + "file dir {}/{}".format( + self.file_system, remote_file + ) + remote_out = self.ssh_ctl_chan.send_command(remote_cmd) + + if "File Not Found" in remote_out: + raise IOError("Unable to find file on remote system") + + # Parse dir output for filename. Output format is: + # "10/16/2019 10:00p 6738 {filename}" + + pattern = r"\S+\s+\S+\s+(\d+)\s+{}".format(re.escape(remote_file)) + match = re.search(pattern, remote_out) + + if not match: + raise ValueError("Filename entry not found in dir output") + + file_size = int(match.group(1)) + return file_size + + def verify_file(self): + """Verify the file has been transferred correctly based on filesize.""" + if self.direction == "put": + return os.stat(self.source_file).st_size == self.remote_file_size( + remote_file=self.dest_file + ) + elif self.direction == "get": + return ( + self.remote_file_size(remote_file=self.source_file) + == os.stat(self.dest_file).st_size + ) + + def file_md5(self, **kwargs): + raise AttributeError("SR-OS does not support an MD5-hash operation.") + + def process_md5(self, **kwargs): + raise AttributeError("SR-OS does not support an MD5-hash operation.") + + def compare_md5(self, **kwargs): + raise AttributeError("SR-OS does not support an MD5-hash operation.") + + def remote_md5(self, **kwargs): + raise AttributeError("SR-OS does not support an MD5-hash operation.") diff --git a/netmiko/scp_functions.py b/netmiko/scp_functions.py index 452fcd781..b1f1357db 100644 --- a/netmiko/scp_functions.py +++ b/netmiko/scp_functions.py @@ -27,6 +27,7 @@ def file_transfer( inline_transfer=False, overwrite_file=False, socket_timeout=10.0, + verify_file=None, ): """Use Secure Copy or Inline (IOS-only) to transfer files to/from network devices. @@ -61,6 +62,10 @@ def file_transfer( if not cisco_ios and inline_transfer: raise ValueError("Inline Transfer only supported for Cisco IOS/Cisco IOS-XE") + # Replace disable_md5 argument with verify_file argument across time + if verify_file is None: + verify_file = not disable_md5 + scp_args = { "ssh_conn": ssh_conn, "source_file": source_file, @@ -76,13 +81,13 @@ def file_transfer( with TransferClass(**scp_args) as scp_transfer: if scp_transfer.check_file_exists(): if overwrite_file: - if not disable_md5: - if scp_transfer.compare_md5(): + if verify_file: + if scp_transfer.verify_file(): return nottransferred_but_verified else: # File exists, you can overwrite it, MD5 is wrong (transfer file) verifyspace_and_transferfile(scp_transfer) - if scp_transfer.compare_md5(): + if scp_transfer.verify_file(): return transferred_and_verified else: raise ValueError( @@ -94,16 +99,16 @@ def file_transfer( return transferred_and_notverified else: # File exists, but you can't overwrite it. - if not disable_md5: - if scp_transfer.compare_md5(): + if verify_file: + if scp_transfer.verify_file(): return nottransferred_but_verified msg = "File already exists and overwrite_file is disabled" raise ValueError(msg) else: verifyspace_and_transferfile(scp_transfer) # File doesn't exist - if not disable_md5: - if scp_transfer.compare_md5(): + if verify_file: + if scp_transfer.verify_file(): return transferred_and_verified else: raise ValueError("MD5 failure between source and destination files") diff --git a/netmiko/scp_handler.py b/netmiko/scp_handler.py index d4cebcd35..456473eb6 100644 --- a/netmiko/scp_handler.py +++ b/netmiko/scp_handler.py @@ -64,6 +64,7 @@ def __init__( file_system=None, direction="put", socket_timeout=10.0, + hash_supported=True, ): self.ssh_ctl_chan = ssh_conn self.source_file = source_file @@ -85,10 +86,12 @@ def __init__( self.file_system = file_system if direction == "put": - self.source_md5 = self.file_md5(source_file) + self.source_md5 = self.file_md5(source_file) if hash_supported else None self.file_size = os.stat(source_file).st_size elif direction == "get": - self.source_md5 = self.remote_md5(remote_file=source_file) + self.source_md5 = ( + self.remote_md5(remote_file=source_file) if hash_supported else None + ) self.file_size = self.remote_file_size(remote_file=source_file) else: raise ValueError("Invalid direction specified") diff --git a/netmiko/ssh_dispatcher.py b/netmiko/ssh_dispatcher.py old mode 100644 new mode 100755 index ccbdf7811..f45064914 --- a/netmiko/ssh_dispatcher.py +++ b/netmiko/ssh_dispatcher.py @@ -62,7 +62,7 @@ from netmiko.mrv import MrvLxSSH from netmiko.mrv import MrvOptiswitchSSH from netmiko.netapp import NetAppcDotSSH -from netmiko.nokia import NokiaSrosSSH +from netmiko.nokia import NokiaSrosSSH, NokiaSrosFileTransfer from netmiko.oneaccess import OneaccessOneOSTelnet, OneaccessOneOSSSH from netmiko.ovs import OvsLinuxSSH from netmiko.paloalto import PaloAltoPanosSSH @@ -189,6 +189,7 @@ "dell_os10": DellOS10FileTransfer, "juniper_junos": JuniperFileTransfer, "linux": LinuxFileTransfer, + "nokia_sros": NokiaSrosFileTransfer, } # Also support keys that end in _ssh diff --git a/tests/conftest.py b/tests/conftest.py index 1150a9455..14c010d34 100755 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -223,6 +223,20 @@ def delete_file_ciena_saos(ssh_conn, dest_file_system, dest_file): return output +def delete_file_nokia_sros(ssh_conn, dest_file_system, dest_file): + """Delete a remote file for a Nokia SR OS device.""" + full_file_name = "{}/{}".format(dest_file_system, dest_file) + cmd = "file delete {} force".format(full_file_name) + cmd_prefix = "" + if "@" in ssh_conn.base_prompt: + cmd_prefix = "//" + ssh_conn.send_command(cmd_prefix + "environment no more") + output = ssh_conn.send_command_timing( + cmd_prefix + cmd, strip_command=False, strip_prompt=False + ) + return output + + @pytest.fixture(scope="module") def scp_fixture(request): """ @@ -474,4 +488,9 @@ def get_platform_args(): "enable_scp": False, "delete_file": delete_file_ciena_saos, }, + "nokia_sros": { + "file_system": "cf3:", + "enable_scp": False, + "delete_file": delete_file_nokia_sros, + }, } diff --git a/tests/test_netmiko_scp.py b/tests/test_netmiko_scp.py index b480f4a01..fb90d6435 100755 --- a/tests/test_netmiko_scp.py +++ b/tests/test_netmiko_scp.py @@ -2,17 +2,6 @@ import pytest from netmiko import file_transfer -# def test_enable_scp(scp_fixture): -# ssh_conn, scp_transfer = scp_fixture -# -# scp_transfer.disable_scp() -# output = ssh_conn.send_command_expect("show run | inc scp") -# assert 'ip scp server enable' not in output -# -# scp_transfer.enable_scp() -# output = ssh_conn.send_command_expect("show run | inc scp") -# assert 'ip scp server enable' in output - def test_scp_put(scp_fixture): ssh_conn, scp_transfer = scp_fixture @@ -53,6 +42,8 @@ def test_remote_file_size(scp_fixture): def test_md5_methods(scp_fixture): ssh_conn, scp_transfer = scp_fixture + if "nokia_sros" in ssh_conn.device_type: + pytest.skip("MD5 not supported on this platform") md5_value = "d8df36973ff832b564ad84642d07a261" remote_md5 = scp_transfer.remote_md5() @@ -90,6 +81,8 @@ def test_scp_get(scp_fixture_get): def test_md5_methods_get(scp_fixture_get): ssh_conn, scp_transfer = scp_fixture_get + if "nokia_sros" in ssh_conn.device_type: + pytest.skip("MD5 not supported on this platform") md5_value = "d8df36973ff832b564ad84642d07a261" local_md5 = scp_transfer.file_md5("test9.txt") assert local_md5 == md5_value From 3832f588f7ef4ca341f57633d24ad182a44c69f4 Mon Sep 17 00:00:00 2001 From: Patryk Szulczewski Date: Mon, 11 May 2020 17:37:45 +0200 Subject: [PATCH 37/39] SCP nokia_sros: Fix to check_file_exists() to support sub-directories (#1724) --- netmiko/nokia/nokia_sros_ssh.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netmiko/nokia/nokia_sros_ssh.py b/netmiko/nokia/nokia_sros_ssh.py index 97fbe544a..4d71ef016 100755 --- a/netmiko/nokia/nokia_sros_ssh.py +++ b/netmiko/nokia/nokia_sros_ssh.py @@ -222,10 +222,11 @@ def check_file_exists(self, remote_cmd=""): remote_cmd = self._file_cmd_prefix() + "file dir {}/{}".format( self.file_system, self.dest_file ) + dest_file_name = self.dest_file.replace("\\", "/").split("/")[-1] remote_out = self.ssh_ctl_chan.send_command(remote_cmd) if "File Not Found" in remote_out: return False - elif self.dest_file in remote_out: + elif dest_file_name in remote_out: return True else: raise ValueError("Unexpected output from check_file_exists") From ef78483aded4fdd93217e7d73a817d1a2c2a9c2c Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Fri, 15 May 2020 14:03:34 -0700 Subject: [PATCH 38/39] Fixing NX-OS issue with double slashes and SCP (#1727) --- netmiko/cisco/cisco_nxos_ssh.py | 8 ++++---- netmiko/ssh_autodetect.py | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/netmiko/cisco/cisco_nxos_ssh.py b/netmiko/cisco/cisco_nxos_ssh.py index 54da3d909..d1c67eff5 100644 --- a/netmiko/cisco/cisco_nxos_ssh.py +++ b/netmiko/cisco/cisco_nxos_ssh.py @@ -89,6 +89,8 @@ def remote_file_size(self, remote_cmd="", remote_file=None): remote_cmd = f"dir {self.file_system}/{remote_file}" remote_out = self.ssh_ctl_chan.send_command(remote_cmd) + if re.search("no such file or directory", remote_out, flags=re.I): + raise IOError("Unable to find file on remote system") # Match line containing file name escape_file_name = re.escape(remote_file) pattern = r".*({}).*".format(escape_file_name) @@ -96,12 +98,10 @@ def remote_file_size(self, remote_cmd="", remote_file=None): if match: file_size = match.group(0) file_size = file_size.split()[0] - - if "No such file or directory" in remote_out: - raise IOError("Unable to find file on remote system") - else: return int(file_size) + raise IOError("Unable to find file on remote system") + @staticmethod def process_md5(md5_output, pattern=r"= (.*)"): """Not needed on NX-OS.""" diff --git a/netmiko/ssh_autodetect.py b/netmiko/ssh_autodetect.py index ca46550c5..c9e65f0d7 100644 --- a/netmiko/ssh_autodetect.py +++ b/netmiko/ssh_autodetect.py @@ -315,7 +315,7 @@ def _send_command_wrapper(self, cmd): return cached_results def _autodetect_remote_version( - self, search_patterns=None, re_flags=re.I, priority=99 + self, search_patterns=None, re_flags=re.IGNORECASE, priority=99 ): """ Method to try auto-detect the device type, by matching a regular expression on the reported @@ -350,7 +350,9 @@ def _autodetect_remote_version( return 0 return 0 - def _autodetect_std(self, cmd="", search_patterns=None, re_flags=re.I, priority=99): + def _autodetect_std( + self, cmd="", search_patterns=None, re_flags=re.IGNORECASE, priority=99 + ): """ Standard method to try to auto-detect the device type. This method will be called for each device_type present in SSH_MAPPER_BASE dict ('dispatch' key). It will attempt to send a From 719cf274ce014905353f6998212eb7e5f30d8289 Mon Sep 17 00:00:00 2001 From: Kirk Byers Date: Mon, 18 May 2020 15:12:48 -0700 Subject: [PATCH 39/39] Release 3.1.1 (#1733) --- docs/netmiko/aruba/aruba_ssh.html | 38 +- docs/netmiko/aruba/index.html | 27 +- docs/netmiko/base_connection.html | 118 ++--- docs/netmiko/cisco/cisco_asa_ssh.html | 11 +- docs/netmiko/cisco/cisco_ios.html | 2 +- docs/netmiko/cisco/cisco_nxos_ssh.html | 16 +- docs/netmiko/cisco/cisco_xr.html | 2 +- docs/netmiko/cisco/index.html | 20 +- docs/netmiko/cisco_base_connection.html | 3 +- docs/netmiko/huawei/huawei.html | 15 +- docs/netmiko/huawei/huawei_smartax.html | 488 +++++++++++++++++++++ docs/netmiko/huawei/index.html | 361 ++++++++++++++- docs/netmiko/index.html | 144 ++++-- docs/netmiko/nokia/index.html | 233 +++++++++- docs/netmiko/nokia/nokia_sros_ssh.html | 358 ++++++++++++++- docs/netmiko/scp_functions.html | 40 +- docs/netmiko/scp_handler.html | 17 +- docs/netmiko/ssh_autodetect.html | 106 ++++- docs/netmiko/ubiquiti/edge_ssh.html | 4 + docs/netmiko/ubiquiti/index.html | 282 +++++++++++- docs/netmiko/ubiquiti/unifiswitch_ssh.html | 373 ++++++++++++++++ docs/netmiko/vyos/index.html | 2 + docs/netmiko/vyos/vyos_ssh.html | 3 + netmiko/__init__.py | 2 +- tests/test_suite_alt.sh | 6 + 25 files changed, 2495 insertions(+), 176 deletions(-) create mode 100644 docs/netmiko/huawei/huawei_smartax.html create mode 100644 docs/netmiko/ubiquiti/unifiswitch_ssh.html diff --git a/docs/netmiko/aruba/aruba_ssh.html b/docs/netmiko/aruba/aruba_ssh.html index 1998d22f2..a19342dd4 100644 --- a/docs/netmiko/aruba/aruba_ssh.html +++ b/docs/netmiko/aruba/aruba_ssh.html @@ -35,6 +35,9 @@

Module netmiko.aruba.aruba_ssh

def __init__(self, **kwargs): if kwargs.get("default_enter") is None: kwargs["default_enter"] = "\r" + # Aruba has an auto-complete on space behavior that is problematic + if kwargs.get("global_cmd_verify") is None: + kwargs["global_cmd_verify"] = False return super().__init__(**kwargs) def session_preparation(self): @@ -57,7 +60,13 @@

Module netmiko.aruba.aruba_ssh

""" if not pattern: pattern = re.escape(self.base_prompt[:16]) - return super().check_config_mode(check_string=check_string, pattern=pattern) + return super().check_config_mode(check_string=check_string, pattern=pattern) + + def config_mode(self, config_command="configure term", pattern=""): + """ + Aruba auto completes on space so 'configure' needs fully spelled-out. + """ + return super().config_mode(config_command=config_command, pattern=pattern)
@@ -209,6 +218,9 @@

Classes

def __init__(self, **kwargs): if kwargs.get("default_enter") is None: kwargs["default_enter"] = "\r" + # Aruba has an auto-complete on space behavior that is problematic + if kwargs.get("global_cmd_verify") is None: + kwargs["global_cmd_verify"] = False return super().__init__(**kwargs) def session_preparation(self): @@ -231,7 +243,13 @@

Classes

""" if not pattern: pattern = re.escape(self.base_prompt[:16]) - return super().check_config_mode(check_string=check_string, pattern=pattern) + return super().check_config_mode(check_string=check_string, pattern=pattern) + + def config_mode(self, config_command="configure term", pattern=""): + """ + Aruba auto completes on space so 'configure' needs fully spelled-out. + """ + return super().config_mode(config_command=config_command, pattern=pattern)

Ancestors

    @@ -260,6 +278,20 @@

    Methods

    return super().check_config_mode(check_string=check_string, pattern=pattern) +
    +def config_mode(self, config_command='configure term', pattern='') +
    +
    +

    Aruba auto completes on space so 'configure' needs fully spelled-out.

    +
    +Source code +
    def config_mode(self, config_command="configure term", pattern=""):
    +    """
    +    Aruba auto completes on space so 'configure' needs fully spelled-out.
    +    """
    +    return super().config_mode(config_command=config_command, pattern=pattern)
    +
    +
    def session_preparation(self)
    @@ -290,7 +322,6 @@

    Inherited members

  • clear_buffer
  • close_session_log
  • commit
  • -
  • config_mode
  • disable_paging
  • disconnect
  • enable
  • @@ -347,6 +378,7 @@

    Index

    ArubaSSH

    diff --git a/docs/netmiko/aruba/index.html b/docs/netmiko/aruba/index.html index 86f081d69..43750fefd 100644 --- a/docs/netmiko/aruba/index.html +++ b/docs/netmiko/aruba/index.html @@ -183,6 +183,9 @@

    Classes

    def __init__(self, **kwargs): if kwargs.get("default_enter") is None: kwargs["default_enter"] = "\r" + # Aruba has an auto-complete on space behavior that is problematic + if kwargs.get("global_cmd_verify") is None: + kwargs["global_cmd_verify"] = False return super().__init__(**kwargs) def session_preparation(self): @@ -205,7 +208,13 @@

    Classes

    """ if not pattern: pattern = re.escape(self.base_prompt[:16]) - return super().check_config_mode(check_string=check_string, pattern=pattern) + return super().check_config_mode(check_string=check_string, pattern=pattern) + + def config_mode(self, config_command="configure term", pattern=""): + """ + Aruba auto completes on space so 'configure' needs fully spelled-out. + """ + return super().config_mode(config_command=config_command, pattern=pattern)

    Ancestors

      @@ -234,6 +243,20 @@

      Methods

      return super().check_config_mode(check_string=check_string, pattern=pattern) +
      +def config_mode(self, config_command='configure term', pattern='') +
      +
      +

      Aruba auto completes on space so 'configure' needs fully spelled-out.

      +
      +Source code +
      def config_mode(self, config_command="configure term", pattern=""):
      +    """
      +    Aruba auto completes on space so 'configure' needs fully spelled-out.
      +    """
      +    return super().config_mode(config_command=config_command, pattern=pattern)
      +
      +
      def session_preparation(self)
      @@ -264,7 +287,6 @@

      Inherited members

    • clear_buffer
    • close_session_log
    • commit
    • -
    • config_mode
    • disable_paging
    • disconnect
    • enable
    • @@ -326,6 +348,7 @@

      Index

      ArubaSSH

      diff --git a/docs/netmiko/base_connection.html b/docs/netmiko/base_connection.html index b4e7244b8..b130f3029 100644 --- a/docs/netmiko/base_connection.html +++ b/docs/netmiko/base_connection.html @@ -898,18 +898,16 @@

      Module netmiko.base_connection

      output = self.strip_prompt(output) return output - def establish_connection(self, width=None, height=None): + def establish_connection(self, width=511, height=1000): """Establish SSH connection to the network device Timeout will generate a NetmikoTimeoutException Authentication failure will generate a NetmikoAuthenticationException - width and height are needed for Fortinet paging setting. - - :param width: Specified width of the VT100 terminal window + :param width: Specified width of the VT100 terminal window (default: 511) :type width: int - :param height: Specified height of the VT100 terminal window + :param height: Specified height of the VT100 terminal window (default: 1000) :type height: int """ if self.protocol == "telnet": @@ -945,12 +943,9 @@

      Module netmiko.base_connection

      print(f"SSH connection established to {self.host}:{self.port}") # Use invoke_shell to establish an 'interactive session' - if width and height: - self.remote_conn = self.remote_conn_pre.invoke_shell( - term="vt100", width=width, height=height - ) - else: - self.remote_conn = self.remote_conn_pre.invoke_shell() + self.remote_conn = self.remote_conn_pre.invoke_shell( + term="vt100", width=width, height=height + ) self.remote_conn.settimeout(self.blocking_timeout) if self.keepalive: @@ -1056,7 +1051,10 @@

      Module netmiko.base_connection

      log.debug(f"Command: {command}") self.write_channel(command) # Make sure you read until you detect the command echo (avoid getting out of sync) - output = self.read_until_pattern(pattern=re.escape(command.strip())) + if self.global_cmd_verify is not False: + output = self.read_until_pattern(pattern=re.escape(command.strip())) + else: + output = self.read_until_prompt() log.debug(f"{output}") log.debug("Exiting disable_paging") return output @@ -1079,7 +1077,10 @@

      Module netmiko.base_connection

      command = self.normalize_cmd(command) self.write_channel(command) # Make sure you read until you detect the command echo (avoid getting out of sync) - output = self.read_until_pattern(pattern=re.escape(command.strip())) + if self.global_cmd_verify is not False: + output = self.read_until_pattern(pattern=re.escape(command.strip())) + else: + output = self.read_until_prompt() return output def set_base_prompt( @@ -1646,7 +1647,10 @@

      Module netmiko.base_connection

      if not self.check_config_mode(): self.write_channel(self.normalize_cmd(config_command)) # Make sure you read until you detect the command echo (avoid getting out of sync) - output += self.read_until_pattern(pattern=re.escape(config_command.strip())) + if self.global_cmd_verify is not False: + output += self.read_until_pattern( + pattern=re.escape(config_command.strip()) + ) if not re.search(pattern, output, flags=re.M): output += self.read_until_pattern(pattern=pattern) if not self.check_config_mode(): @@ -1666,7 +1670,10 @@

      Module netmiko.base_connection

      if self.check_config_mode(): self.write_channel(self.normalize_cmd(exit_config)) # Make sure you read until you detect the command echo (avoid getting out of sync) - output += self.read_until_pattern(pattern=re.escape(exit_config.strip())) + if self.global_cmd_verify is not False: + output += self.read_until_pattern( + pattern=re.escape(exit_config.strip()) + ) if not re.search(pattern, output, flags=re.M): output += self.read_until_pattern(pattern=pattern) if self.check_config_mode(): @@ -2924,18 +2931,16 @@

      Classes

      output = self.strip_prompt(output) return output - def establish_connection(self, width=None, height=None): + def establish_connection(self, width=511, height=1000): """Establish SSH connection to the network device Timeout will generate a NetmikoTimeoutException Authentication failure will generate a NetmikoAuthenticationException - width and height are needed for Fortinet paging setting. - - :param width: Specified width of the VT100 terminal window + :param width: Specified width of the VT100 terminal window (default: 511) :type width: int - :param height: Specified height of the VT100 terminal window + :param height: Specified height of the VT100 terminal window (default: 1000) :type height: int """ if self.protocol == "telnet": @@ -2971,12 +2976,9 @@

      Classes

      print(f"SSH connection established to {self.host}:{self.port}") # Use invoke_shell to establish an 'interactive session' - if width and height: - self.remote_conn = self.remote_conn_pre.invoke_shell( - term="vt100", width=width, height=height - ) - else: - self.remote_conn = self.remote_conn_pre.invoke_shell() + self.remote_conn = self.remote_conn_pre.invoke_shell( + term="vt100", width=width, height=height + ) self.remote_conn.settimeout(self.blocking_timeout) if self.keepalive: @@ -3082,7 +3084,10 @@

      Classes

      log.debug(f"Command: {command}") self.write_channel(command) # Make sure you read until you detect the command echo (avoid getting out of sync) - output = self.read_until_pattern(pattern=re.escape(command.strip())) + if self.global_cmd_verify is not False: + output = self.read_until_pattern(pattern=re.escape(command.strip())) + else: + output = self.read_until_prompt() log.debug(f"{output}") log.debug("Exiting disable_paging") return output @@ -3105,7 +3110,10 @@

      Classes

      command = self.normalize_cmd(command) self.write_channel(command) # Make sure you read until you detect the command echo (avoid getting out of sync) - output = self.read_until_pattern(pattern=re.escape(command.strip())) + if self.global_cmd_verify is not False: + output = self.read_until_pattern(pattern=re.escape(command.strip())) + else: + output = self.read_until_prompt() return output def set_base_prompt( @@ -3672,7 +3680,10 @@

      Classes

      if not self.check_config_mode(): self.write_channel(self.normalize_cmd(config_command)) # Make sure you read until you detect the command echo (avoid getting out of sync) - output += self.read_until_pattern(pattern=re.escape(config_command.strip())) + if self.global_cmd_verify is not False: + output += self.read_until_pattern( + pattern=re.escape(config_command.strip()) + ) if not re.search(pattern, output, flags=re.M): output += self.read_until_pattern(pattern=pattern) if not self.check_config_mode(): @@ -3692,7 +3703,10 @@

      Classes

      if self.check_config_mode(): self.write_channel(self.normalize_cmd(exit_config)) # Make sure you read until you detect the command echo (avoid getting out of sync) - output += self.read_until_pattern(pattern=re.escape(exit_config.strip())) + if self.global_cmd_verify is not False: + output += self.read_until_pattern( + pattern=re.escape(exit_config.strip()) + ) if not re.search(pattern, output, flags=re.M): output += self.read_until_pattern(pattern=pattern) if self.check_config_mode(): @@ -4143,7 +4157,10 @@

      Methods

      if not self.check_config_mode(): self.write_channel(self.normalize_cmd(config_command)) # Make sure you read until you detect the command echo (avoid getting out of sync) - output += self.read_until_pattern(pattern=re.escape(config_command.strip())) + if self.global_cmd_verify is not False: + output += self.read_until_pattern( + pattern=re.escape(config_command.strip()) + ) if not re.search(pattern, output, flags=re.M): output += self.read_until_pattern(pattern=pattern) if not self.check_config_mode(): @@ -4179,7 +4196,10 @@

      Methods

      log.debug(f"Command: {command}") self.write_channel(command) # Make sure you read until you detect the command echo (avoid getting out of sync) - output = self.read_until_pattern(pattern=re.escape(command.strip())) + if self.global_cmd_verify is not False: + output = self.read_until_pattern(pattern=re.escape(command.strip())) + else: + output = self.read_until_prompt() log.debug(f"{output}") log.debug("Exiting disable_paging") return output @@ -4257,31 +4277,28 @@

      Methods

      -def establish_connection(self, width=None, height=None) +def establish_connection(self, width=511, height=1000)

      Establish SSH connection to the network device

      Timeout will generate a NetmikoTimeoutException Authentication failure will generate a NetmikoAuthenticationException

      -

      width and height are needed for Fortinet paging setting.

      -

      :param width: Specified width of the VT100 terminal window +

      :param width: Specified width of the VT100 terminal window (default: 511) :type width: int

      -

      :param height: Specified height of the VT100 terminal window +

      :param height: Specified height of the VT100 terminal window (default: 1000) :type height: int

      Source code -
      def establish_connection(self, width=None, height=None):
      +
      def establish_connection(self, width=511, height=1000):
           """Establish SSH connection to the network device
       
           Timeout will generate a NetmikoTimeoutException
           Authentication failure will generate a NetmikoAuthenticationException
       
      -    width and height are needed for Fortinet paging setting.
      -
      -    :param width: Specified width of the VT100 terminal window
      +    :param width: Specified width of the VT100 terminal window (default: 511)
           :type width: int
       
      -    :param height: Specified height of the VT100 terminal window
      +    :param height: Specified height of the VT100 terminal window (default: 1000)
           :type height: int
           """
           if self.protocol == "telnet":
      @@ -4317,12 +4334,9 @@ 

      Methods

      print(f"SSH connection established to {self.host}:{self.port}") # Use invoke_shell to establish an 'interactive session' - if width and height: - self.remote_conn = self.remote_conn_pre.invoke_shell( - term="vt100", width=width, height=height - ) - else: - self.remote_conn = self.remote_conn_pre.invoke_shell() + self.remote_conn = self.remote_conn_pre.invoke_shell( + term="vt100", width=width, height=height + ) self.remote_conn.settimeout(self.blocking_timeout) if self.keepalive: @@ -4357,7 +4371,10 @@

      Methods

      if self.check_config_mode(): self.write_channel(self.normalize_cmd(exit_config)) # Make sure you read until you detect the command echo (avoid getting out of sync) - output += self.read_until_pattern(pattern=re.escape(exit_config.strip())) + if self.global_cmd_verify is not False: + output += self.read_until_pattern( + pattern=re.escape(exit_config.strip()) + ) if not re.search(pattern, output, flags=re.M): output += self.read_until_pattern(pattern=pattern) if self.check_config_mode(): @@ -5356,7 +5373,10 @@

      Methods

      command = self.normalize_cmd(command) self.write_channel(command) # Make sure you read until you detect the command echo (avoid getting out of sync) - output = self.read_until_pattern(pattern=re.escape(command.strip())) + if self.global_cmd_verify is not False: + output = self.read_until_pattern(pattern=re.escape(command.strip())) + else: + output = self.read_until_prompt() return output
      diff --git a/docs/netmiko/cisco/cisco_asa_ssh.html b/docs/netmiko/cisco/cisco_asa_ssh.html index 1152d74ac..c2b09f108 100644 --- a/docs/netmiko/cisco/cisco_asa_ssh.html +++ b/docs/netmiko/cisco/cisco_asa_ssh.html @@ -47,6 +47,9 @@

      Module netmiko.cisco.cisco_asa_ssh

      except ValueError: # Don't fail for the terminal width pass + else: + # Disable cmd_verify if the terminal width can't be set + self.global_cmd_verify = False # Clear the read buffer time.sleep(0.3 * self.global_delay_factor) @@ -157,7 +160,7 @@

      Classes

      class CiscoAsaFileTransfer -(ssh_conn, source_file, dest_file, file_system=None, direction='put', socket_timeout=10.0) +(ssh_conn, source_file, dest_file, file_system=None, direction='put', socket_timeout=10.0, hash_supported=True)

      Cisco ASA SCP File Transfer driver.

      @@ -350,6 +353,9 @@

      Inherited members

      except ValueError: # Don't fail for the terminal width pass + else: + # Disable cmd_verify if the terminal width can't be set + self.global_cmd_verify = False # Clear the read buffer time.sleep(0.3 * self.global_delay_factor) @@ -589,6 +595,9 @@

      Methods

      except ValueError: # Don't fail for the terminal width pass + else: + # Disable cmd_verify if the terminal width can't be set + self.global_cmd_verify = False # Clear the read buffer time.sleep(0.3 * self.global_delay_factor) diff --git a/docs/netmiko/cisco/cisco_ios.html b/docs/netmiko/cisco/cisco_ios.html index 60dc784fb..1409834e9 100644 --- a/docs/netmiko/cisco/cisco_ios.html +++ b/docs/netmiko/cisco/cisco_ios.html @@ -519,7 +519,7 @@

      Inherited members

      class CiscoIosFileTransfer -(ssh_conn, source_file, dest_file, file_system=None, direction='put', socket_timeout=10.0) +(ssh_conn, source_file, dest_file, file_system=None, direction='put', socket_timeout=10.0, hash_supported=True)

      Cisco IOS SCP File Transfer driver.

      diff --git a/docs/netmiko/cisco/cisco_nxos_ssh.html b/docs/netmiko/cisco/cisco_nxos_ssh.html index 8de9cc6a1..9079cba9f 100644 --- a/docs/netmiko/cisco/cisco_nxos_ssh.html +++ b/docs/netmiko/cisco/cisco_nxos_ssh.html @@ -113,6 +113,8 @@

      Module netmiko.cisco.cisco_nxos_ssh

      remote_cmd = f"dir {self.file_system}/{remote_file}" remote_out = self.ssh_ctl_chan.send_command(remote_cmd) + if re.search("no such file or directory", remote_out, flags=re.I): + raise IOError("Unable to find file on remote system") # Match line containing file name escape_file_name = re.escape(remote_file) pattern = r".*({}).*".format(escape_file_name) @@ -120,12 +122,10 @@

      Module netmiko.cisco.cisco_nxos_ssh

      if match: file_size = match.group(0) file_size = file_size.split()[0] - - if "No such file or directory" in remote_out: - raise IOError("Unable to find file on remote system") - else: return int(file_size) + raise IOError("Unable to find file on remote system") + @staticmethod def process_md5(md5_output, pattern=r"= (.*)"): """Not needed on NX-OS.""" @@ -225,6 +225,8 @@

      Classes

      remote_cmd = f"dir {self.file_system}/{remote_file}" remote_out = self.ssh_ctl_chan.send_command(remote_cmd) + if re.search("no such file or directory", remote_out, flags=re.I): + raise IOError("Unable to find file on remote system") # Match line containing file name escape_file_name = re.escape(remote_file) pattern = r".*({}).*".format(escape_file_name) @@ -232,12 +234,10 @@

      Classes

      if match: file_size = match.group(0) file_size = file_size.split()[0] - - if "No such file or directory" in remote_out: - raise IOError("Unable to find file on remote system") - else: return int(file_size) + raise IOError("Unable to find file on remote system") + @staticmethod def process_md5(md5_output, pattern=r"= (.*)"): """Not needed on NX-OS.""" diff --git a/docs/netmiko/cisco/cisco_xr.html b/docs/netmiko/cisco/cisco_xr.html index e9cbdee74..8f39184e0 100644 --- a/docs/netmiko/cisco/cisco_xr.html +++ b/docs/netmiko/cisco/cisco_xr.html @@ -771,7 +771,7 @@

      Inherited members

      class CiscoXrFileTransfer -(ssh_conn, source_file, dest_file, file_system=None, direction='put', socket_timeout=10.0) +(ssh_conn, source_file, dest_file, file_system=None, direction='put', socket_timeout=10.0, hash_supported=True)

      Cisco IOS-XR SCP File Transfer driver.

      diff --git a/docs/netmiko/cisco/index.html b/docs/netmiko/cisco/index.html index 1e9d5dcdc..b226e88b0 100644 --- a/docs/netmiko/cisco/index.html +++ b/docs/netmiko/cisco/index.html @@ -101,7 +101,7 @@

      Classes

      class CiscoAsaFileTransfer -(ssh_conn, source_file, dest_file, file_system=None, direction='put', socket_timeout=10.0) +(ssh_conn, source_file, dest_file, file_system=None, direction='put', socket_timeout=10.0, hash_supported=True)

      Cisco ASA SCP File Transfer driver.

      @@ -294,6 +294,9 @@

      Inherited members

      except ValueError: # Don't fail for the terminal width pass + else: + # Disable cmd_verify if the terminal width can't be set + self.global_cmd_verify = False # Clear the read buffer time.sleep(0.3 * self.global_delay_factor) @@ -533,6 +536,9 @@

      Methods

      except ValueError: # Don't fail for the terminal width pass + else: + # Disable cmd_verify if the terminal width can't be set + self.global_cmd_verify = False # Clear the read buffer time.sleep(0.3 * self.global_delay_factor) @@ -865,7 +871,7 @@

      Inherited members

      class CiscoIosFileTransfer -(ssh_conn, source_file, dest_file, file_system=None, direction='put', socket_timeout=10.0) +(ssh_conn, source_file, dest_file, file_system=None, direction='put', socket_timeout=10.0, hash_supported=True)

      Cisco IOS SCP File Transfer driver.

      @@ -1558,6 +1564,8 @@

      Inherited members

      remote_cmd = f"dir {self.file_system}/{remote_file}" remote_out = self.ssh_ctl_chan.send_command(remote_cmd) + if re.search("no such file or directory", remote_out, flags=re.I): + raise IOError("Unable to find file on remote system") # Match line containing file name escape_file_name = re.escape(remote_file) pattern = r".*({}).*".format(escape_file_name) @@ -1565,12 +1573,10 @@

      Inherited members

      if match: file_size = match.group(0) file_size = file_size.split()[0] - - if "No such file or directory" in remote_out: - raise IOError("Unable to find file on remote system") - else: return int(file_size) + raise IOError("Unable to find file on remote system") + @staticmethod def process_md5(md5_output, pattern=r"= (.*)"): """Not needed on NX-OS.""" @@ -3129,7 +3135,7 @@

      Inherited members

      class CiscoXrFileTransfer -(ssh_conn, source_file, dest_file, file_system=None, direction='put', socket_timeout=10.0) +(ssh_conn, source_file, dest_file, file_system=None, direction='put', socket_timeout=10.0, hash_supported=True)

      Cisco IOS-XR SCP File Transfer driver.

      diff --git a/docs/netmiko/cisco_base_connection.html b/docs/netmiko/cisco_base_connection.html index 0724241e2..29ae6d36b 100644 --- a/docs/netmiko/cisco_base_connection.html +++ b/docs/netmiko/cisco_base_connection.html @@ -624,6 +624,7 @@

      Subclasses

    • CiscoXrBase
    • DellPowerConnectBase
    • HuaweiBase
    • +
    • HuaweiSmartAXSSH
    • IpInfusionOcNOSBase
    • OneaccessOneOSBase
    • RuijieOSBase
    • @@ -942,7 +943,7 @@

      Inherited members

      class CiscoFileTransfer -(ssh_conn, source_file, dest_file, file_system=None, direction='put', socket_timeout=10.0) +(ssh_conn, source_file, dest_file, file_system=None, direction='put', socket_timeout=10.0, hash_supported=True)

      Class to manage SCP file transfer and associated SSH control channel.

      diff --git a/docs/netmiko/huawei/huawei.html b/docs/netmiko/huawei/huawei.html index 8208126ee..e521551c0 100644 --- a/docs/netmiko/huawei/huawei.html +++ b/docs/netmiko/huawei/huawei.html @@ -135,8 +135,9 @@

      Module netmiko.huawei.huawei

      def special_login_handler(self): """Handle password change request by ignoring it""" - password_change_prompt = r"(Change now|Please choose 'YES' or 'NO').+" - output = self.read_until_prompt_or_pattern(password_change_prompt) + # Huawei can prompt for password change. Search for that or for normal prompt + password_change_prompt = r"((Change now|Please choose))|([\]>]\s*$)" + output = self.read_until_pattern(password_change_prompt) if re.search(password_change_prompt, output): self.write_channel("N\n") self.clear_buffer() @@ -877,8 +878,9 @@

      Inherited members

      def special_login_handler(self): """Handle password change request by ignoring it""" - password_change_prompt = r"(Change now|Please choose 'YES' or 'NO').+" - output = self.read_until_prompt_or_pattern(password_change_prompt) + # Huawei can prompt for password change. Search for that or for normal prompt + password_change_prompt = r"((Change now|Please choose))|([\]>]\s*$)" + output = self.read_until_pattern(password_change_prompt) if re.search(password_change_prompt, output): self.write_channel("N\n") self.clear_buffer() @@ -906,8 +908,9 @@

      Methods

      def special_login_handler(self):
           """Handle password change request by ignoring it"""
       
      -    password_change_prompt = r"(Change now|Please choose 'YES' or 'NO').+"
      -    output = self.read_until_prompt_or_pattern(password_change_prompt)
      +    # Huawei can prompt for password change. Search for that or for normal prompt
      +    password_change_prompt = r"((Change now|Please choose))|([\]>]\s*$)"
      +    output = self.read_until_pattern(password_change_prompt)
           if re.search(password_change_prompt, output):
               self.write_channel("N\n")
               self.clear_buffer()
      diff --git a/docs/netmiko/huawei/huawei_smartax.html b/docs/netmiko/huawei/huawei_smartax.html
      new file mode 100644
      index 000000000..86dd935ee
      --- /dev/null
      +++ b/docs/netmiko/huawei/huawei_smartax.html
      @@ -0,0 +1,488 @@
      +
      +
      +
      +
      +
      +
      +netmiko.huawei.huawei_smartax API documentation
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +

      Module netmiko.huawei.huawei_smartax

      +
      +
      +
      +Source code +
      import time
      +import re
      +from netmiko.cisco_base_connection import CiscoBaseConnection
      +from netmiko import log
      +
      +
      +class HuaweiSmartAXSSH(CiscoBaseConnection):
      +    """Supports Huawei SmartAX and OLT."""
      +
      +    def session_preparation(self):
      +        """Prepare the session after the connection has been established."""
      +        self.ansi_escape_codes = True
      +        self._test_channel_read()
      +        self.set_base_prompt()
      +        self._disable_smart_interaction()
      +        self.disable_paging()
      +        # Clear the read buffer
      +        time.sleep(0.3 * self.global_delay_factor)
      +        self.clear_buffer()
      +
      +    def strip_ansi_escape_codes(self, string_buffer):
      +        """
      +        Huawei does a strange thing where they add a space and then add ESC[1D
      +        to move the cursor to the left one.
      +        The extra space is problematic.
      +        """
      +        code_cursor_left = chr(27) + r"\[\d+D"
      +        output = string_buffer
      +        pattern = rf" {code_cursor_left}"
      +        output = re.sub(pattern, "", output)
      +
      +        log.debug("Stripping ANSI escape codes")
      +        log.debug(f"new_output = {output}")
      +        log.debug(f"repr = {repr(output)}")
      +        return super().strip_ansi_escape_codes(output)
      +
      +    def _disable_smart_interaction(self, command="undo smart", delay_factor=1):
      +        """Disables the { <cr> } prompt to avoid having to sent a 2nd return after each command"""
      +        delay_factor = self.select_delay_factor(delay_factor)
      +        time.sleep(delay_factor * 0.1)
      +        self.clear_buffer()
      +        command = self.normalize_cmd(command)
      +        log.debug("In disable_smart_interaction")
      +        log.debug(f"Command: {command}")
      +        self.write_channel(command)
      +        if self.global_cmd_verify is not False:
      +            output = self.read_until_pattern(pattern=re.escape(command.strip()))
      +        else:
      +            output = self.read_until_prompt()
      +        log.debug(f"{output}")
      +        log.debug("Exiting disable_smart_interaction")
      +
      +    def disable_paging(self, command="scroll"):
      +        return super().disable_paging(command=command)
      +
      +    def config_mode(self, config_command="config", pattern=""):
      +        """Enter configuration mode."""
      +        return super().config_mode(config_command=config_command, pattern=pattern)
      +
      +    def check_config_mode(self, check_string=")#"):
      +        return super().check_config_mode(check_string=check_string)
      +
      +    def exit_config_mode(self, exit_config="return"):
      +        return super().exit_config_mode(exit_config=exit_config)
      +
      +    def check_enable_mode(self, check_string="#"):
      +        return super().check_enable_mode(check_string=check_string)
      +
      +    def enable(self, cmd="enable", pattern="", re_flags=re.IGNORECASE):
      +        return super().enable(cmd=cmd, pattern=pattern, re_flags=re_flags)
      +
      +    def set_base_prompt(self, pri_prompt_terminator=">", alt_prompt_terminator="#"):
      +        return super().set_base_prompt(
      +            pri_prompt_terminator=pri_prompt_terminator,
      +            alt_prompt_terminator=alt_prompt_terminator,
      +        )
      +
      +    def save_config(self, cmd="save", confirm=False, confirm_response=""):
      +        """ Save Config for HuaweiSSH"""
      +        return super().save_config(
      +            cmd=cmd, confirm=confirm, confirm_response=confirm_response
      +        )
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +

      Classes

      +
      +
      +class HuaweiSmartAXSSH +(ip='', host='', username='', password=None, secret='', port=None, device_type='', verbose=False, global_delay_factor=1, global_cmd_verify=None, use_keys=False, key_file=None, pkey=None, passphrase=None, allow_agent=False, ssh_strict=False, system_host_keys=False, alt_host_keys=False, alt_key_file='', ssh_config_file=None, timeout=100, session_timeout=60, auth_timeout=None, blocking_timeout=20, banner_timeout=15, keepalive=0, default_enter=None, response_return=None, serial_settings=None, fast_cli=False, session_log=None, session_log_record_writes=False, session_log_file_mode='write', allow_auto_change=False, encoding='ascii', sock=None) +
      +
      +

      Supports Huawei SmartAX and OLT.

      +
          Initialize attributes for establishing connection to target device.
      +
      +    :param ip: IP address of target device. Not required if `host` is
      +        provided.
      +    :type ip: str
      +
      +    :param host: Hostname of target device. Not required if `ip` is
      +            provided.
      +    :type host: str
      +
      +    :param username: Username to authenticate against target device if
      +            required.
      +    :type username: str
      +
      +    :param password: Password to authenticate against target device if
      +            required.
      +    :type password: str
      +
      +    :param secret: The enable password if target device requires one.
      +    :type secret: str
      +
      +    :param port: The destination port used to connect to the target
      +            device.
      +    :type port: int or None
      +
      +    :param device_type: Class selection based on device type.
      +    :type device_type: str
      +
      +    :param verbose: Enable additional messages to standard output.
      +    :type verbose: bool
      +
      +    :param global_delay_factor: Multiplication factor affecting Netmiko delays (default: 1).
      +    :type global_delay_factor: int
      +
      +    :param use_keys: Connect to target device using SSH keys.
      +    :type use_keys: bool
      +
      +    :param key_file: Filename path of the SSH key file to use.
      +    :type key_file: str
      +
      +    :param pkey: SSH key object to use.
      +    :type pkey: paramiko.PKey
      +
      +    :param passphrase: Passphrase to use for encrypted key; password will be used for key
      +            decryption if not specified.
      +    :type passphrase: str
      +
      +    :param allow_agent: Enable use of SSH key-agent.
      +    :type allow_agent: bool
      +
      +    :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: Load host keys from the users known_hosts file.
      +    :type system_host_keys: bool
      +    :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).
      +    :type alt_key_file: str
      +
      +    :param ssh_config_file: File name of OpenSSH configuration file.
      +    :type ssh_config_file: str
      +
      +    :param timeout: Connection timeout.
      +    :type timeout: float
      +
      +    :param session_timeout: Set a timeout for parallel requests.
      +    :type session_timeout: float
      +
      +    :param auth_timeout: Set a timeout (in seconds) to wait for an authentication response.
      +    :type auth_timeout: float
      +
      +    :param banner_timeout: Set a timeout to wait for the SSH banner (pass to Paramiko).
      +    :type banner_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
      +
      +    :param default_enter: Character(s) to send to correspond to enter key (default:
      +
      +

      ). +:type default_enter: str

      +
          :param response_return: Character(s) to use in normalized return data to represent
      +            enter key (default:
      +
      +

      ) +:type response_return: str

      +
          :param fast_cli: Provide a way to optimize for performance. Converts select_delay_factor
      +            to select smallest of global and specific. Sets default global_delay_factor to .1
      +            (default: False)
      +    :type fast_cli: boolean
      +
      +    :param session_log: File path or BufferedIOBase subclass object to write the session log to.
      +    :type session_log: str
      +
      +    :param session_log_record_writes: The session log generally only records channel reads due
      +            to eliminate command duplication due to command echo. You can enable this if you
      +            want to record both channel reads and channel writes in the log (default: False).
      +    :type session_log_record_writes: boolean
      +
      +    :param session_log_file_mode: "write" or "append" for session_log file mode
      +            (default: "write")
      +    :type session_log_file_mode: str
      +
      +    :param allow_auto_change: Allow automatic configuration changes for terminal settings.
      +            (default: False)
      +    :type allow_auto_change: bool
      +
      +    :param encoding: Encoding to be used when writing bytes to the output channel.
      +            (default: ascii)
      +    :type encoding: str
      +
      +    :param sock: An open socket or socket-like object (such as a `.Channel`) to use for
      +            communication to the target host (default: None).
      +    :type sock: socket
      +
      +    :param global_cmd_verify: Control whether command echo verification is enabled or disabled
      +            (default: None). Global attribute takes precedence over function `cmd_verify`
      +            argument. Value of `None` indicates to use function `cmd_verify` argument.
      +    :type global_cmd_verify: bool|None
      +
      +
      +Source code +
      class HuaweiSmartAXSSH(CiscoBaseConnection):
      +    """Supports Huawei SmartAX and OLT."""
      +
      +    def session_preparation(self):
      +        """Prepare the session after the connection has been established."""
      +        self.ansi_escape_codes = True
      +        self._test_channel_read()
      +        self.set_base_prompt()
      +        self._disable_smart_interaction()
      +        self.disable_paging()
      +        # Clear the read buffer
      +        time.sleep(0.3 * self.global_delay_factor)
      +        self.clear_buffer()
      +
      +    def strip_ansi_escape_codes(self, string_buffer):
      +        """
      +        Huawei does a strange thing where they add a space and then add ESC[1D
      +        to move the cursor to the left one.
      +        The extra space is problematic.
      +        """
      +        code_cursor_left = chr(27) + r"\[\d+D"
      +        output = string_buffer
      +        pattern = rf" {code_cursor_left}"
      +        output = re.sub(pattern, "", output)
      +
      +        log.debug("Stripping ANSI escape codes")
      +        log.debug(f"new_output = {output}")
      +        log.debug(f"repr = {repr(output)}")
      +        return super().strip_ansi_escape_codes(output)
      +
      +    def _disable_smart_interaction(self, command="undo smart", delay_factor=1):
      +        """Disables the { <cr> } prompt to avoid having to sent a 2nd return after each command"""
      +        delay_factor = self.select_delay_factor(delay_factor)
      +        time.sleep(delay_factor * 0.1)
      +        self.clear_buffer()
      +        command = self.normalize_cmd(command)
      +        log.debug("In disable_smart_interaction")
      +        log.debug(f"Command: {command}")
      +        self.write_channel(command)
      +        if self.global_cmd_verify is not False:
      +            output = self.read_until_pattern(pattern=re.escape(command.strip()))
      +        else:
      +            output = self.read_until_prompt()
      +        log.debug(f"{output}")
      +        log.debug("Exiting disable_smart_interaction")
      +
      +    def disable_paging(self, command="scroll"):
      +        return super().disable_paging(command=command)
      +
      +    def config_mode(self, config_command="config", pattern=""):
      +        """Enter configuration mode."""
      +        return super().config_mode(config_command=config_command, pattern=pattern)
      +
      +    def check_config_mode(self, check_string=")#"):
      +        return super().check_config_mode(check_string=check_string)
      +
      +    def exit_config_mode(self, exit_config="return"):
      +        return super().exit_config_mode(exit_config=exit_config)
      +
      +    def check_enable_mode(self, check_string="#"):
      +        return super().check_enable_mode(check_string=check_string)
      +
      +    def enable(self, cmd="enable", pattern="", re_flags=re.IGNORECASE):
      +        return super().enable(cmd=cmd, pattern=pattern, re_flags=re_flags)
      +
      +    def set_base_prompt(self, pri_prompt_terminator=">", alt_prompt_terminator="#"):
      +        return super().set_base_prompt(
      +            pri_prompt_terminator=pri_prompt_terminator,
      +            alt_prompt_terminator=alt_prompt_terminator,
      +        )
      +
      +    def save_config(self, cmd="save", confirm=False, confirm_response=""):
      +        """ Save Config for HuaweiSSH"""
      +        return super().save_config(
      +            cmd=cmd, confirm=confirm, confirm_response=confirm_response
      +        )
      +
      +

      Ancestors

      + +

      Methods

      +
      +
      +def config_mode(self, config_command='config', pattern='') +
      +
      +

      Enter configuration mode.

      +
      +Source code +
      def config_mode(self, config_command="config", pattern=""):
      +    """Enter configuration mode."""
      +    return super().config_mode(config_command=config_command, pattern=pattern)
      +
      +
      +
      +def save_config(self, cmd='save', confirm=False, confirm_response='') +
      +
      +

      Save Config for HuaweiSSH

      +
      +Source code +
      def save_config(self, cmd="save", confirm=False, confirm_response=""):
      +    """ Save Config for HuaweiSSH"""
      +    return super().save_config(
      +        cmd=cmd, confirm=confirm, confirm_response=confirm_response
      +    )
      +
      +
      +
      +def session_preparation(self) +
      +
      +

      Prepare the session after the connection has been established.

      +
      +Source code +
      def session_preparation(self):
      +    """Prepare the session after the connection has been established."""
      +    self.ansi_escape_codes = True
      +    self._test_channel_read()
      +    self.set_base_prompt()
      +    self._disable_smart_interaction()
      +    self.disable_paging()
      +    # Clear the read buffer
      +    time.sleep(0.3 * self.global_delay_factor)
      +    self.clear_buffer()
      +
      +
      +
      +def strip_ansi_escape_codes(self, string_buffer) +
      +
      +

      Huawei does a strange thing where they add a space and then add ESC[1D +to move the cursor to the left one. +The extra space is problematic.

      +
      +Source code +
      def strip_ansi_escape_codes(self, string_buffer):
      +    """
      +    Huawei does a strange thing where they add a space and then add ESC[1D
      +    to move the cursor to the left one.
      +    The extra space is problematic.
      +    """
      +    code_cursor_left = chr(27) + r"\[\d+D"
      +    output = string_buffer
      +    pattern = rf" {code_cursor_left}"
      +    output = re.sub(pattern, "", output)
      +
      +    log.debug("Stripping ANSI escape codes")
      +    log.debug(f"new_output = {output}")
      +    log.debug(f"repr = {repr(output)}")
      +    return super().strip_ansi_escape_codes(output)
      +
      +
      +
      +

      Inherited members

      + +
      +
      +
      +
      + +
      + + + + + \ No newline at end of file diff --git a/docs/netmiko/huawei/index.html b/docs/netmiko/huawei/index.html index 28005158c..183702529 100644 --- a/docs/netmiko/huawei/index.html +++ b/docs/netmiko/huawei/index.html @@ -24,8 +24,9 @@

      Module netmiko.huawei

      Source code
      from netmiko.huawei.huawei import HuaweiSSH, HuaweiVrpv8SSH
       from netmiko.huawei.huawei import HuaweiTelnet
      +from netmiko.huawei.huawei_smartax import HuaweiSmartAXSSH
       
      -__all__ = ["HuaweiSSH", "HuaweiVrpv8SSH", "HuaweiTelnet"]
      +__all__ = ["HuaweiSmartAXSSH", "HuaweiSSH", "HuaweiVrpv8SSH", "HuaweiTelnet"]
@@ -35,6 +36,10 @@

Sub-modules

+
netmiko.huawei.huawei_smartax
+
+
+
@@ -184,8 +189,9 @@

Classes

def special_login_handler(self): """Handle password change request by ignoring it""" - password_change_prompt = r"(Change now|Please choose 'YES' or 'NO').+" - output = self.read_until_prompt_or_pattern(password_change_prompt) + # Huawei can prompt for password change. Search for that or for normal prompt + password_change_prompt = r"((Change now|Please choose))|([\]>]\s*$)" + output = self.read_until_pattern(password_change_prompt) if re.search(password_change_prompt, output): self.write_channel("N\n") self.clear_buffer() @@ -213,8 +219,9 @@

Methods

def special_login_handler(self):
     """Handle password change request by ignoring it"""
 
-    password_change_prompt = r"(Change now|Please choose 'YES' or 'NO').+"
-    output = self.read_until_prompt_or_pattern(password_change_prompt)
+    # Huawei can prompt for password change. Search for that or for normal prompt
+    password_change_prompt = r"((Change now|Please choose))|([\]>]\s*$)"
+    output = self.read_until_pattern(password_change_prompt)
     if re.search(password_change_prompt, output):
         self.write_channel("N\n")
         self.clear_buffer()
@@ -269,6 +276,340 @@ 

Inherited members

+
+class HuaweiSmartAXSSH +(ip='', host='', username='', password=None, secret='', port=None, device_type='', verbose=False, global_delay_factor=1, global_cmd_verify=None, use_keys=False, key_file=None, pkey=None, passphrase=None, allow_agent=False, ssh_strict=False, system_host_keys=False, alt_host_keys=False, alt_key_file='', ssh_config_file=None, timeout=100, session_timeout=60, auth_timeout=None, blocking_timeout=20, banner_timeout=15, keepalive=0, default_enter=None, response_return=None, serial_settings=None, fast_cli=False, session_log=None, session_log_record_writes=False, session_log_file_mode='write', allow_auto_change=False, encoding='ascii', sock=None) +
+
+

Supports Huawei SmartAX and OLT.

+
    Initialize attributes for establishing connection to target device.
+
+    :param ip: IP address of target device. Not required if `host` is
+        provided.
+    :type ip: str
+
+    :param host: Hostname of target device. Not required if `ip` is
+            provided.
+    :type host: str
+
+    :param username: Username to authenticate against target device if
+            required.
+    :type username: str
+
+    :param password: Password to authenticate against target device if
+            required.
+    :type password: str
+
+    :param secret: The enable password if target device requires one.
+    :type secret: str
+
+    :param port: The destination port used to connect to the target
+            device.
+    :type port: int or None
+
+    :param device_type: Class selection based on device type.
+    :type device_type: str
+
+    :param verbose: Enable additional messages to standard output.
+    :type verbose: bool
+
+    :param global_delay_factor: Multiplication factor affecting Netmiko delays (default: 1).
+    :type global_delay_factor: int
+
+    :param use_keys: Connect to target device using SSH keys.
+    :type use_keys: bool
+
+    :param key_file: Filename path of the SSH key file to use.
+    :type key_file: str
+
+    :param pkey: SSH key object to use.
+    :type pkey: paramiko.PKey
+
+    :param passphrase: Passphrase to use for encrypted key; password will be used for key
+            decryption if not specified.
+    :type passphrase: str
+
+    :param allow_agent: Enable use of SSH key-agent.
+    :type allow_agent: bool
+
+    :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: Load host keys from the users known_hosts file.
+    :type system_host_keys: bool
+    :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).
+    :type alt_key_file: str
+
+    :param ssh_config_file: File name of OpenSSH configuration file.
+    :type ssh_config_file: str
+
+    :param timeout: Connection timeout.
+    :type timeout: float
+
+    :param session_timeout: Set a timeout for parallel requests.
+    :type session_timeout: float
+
+    :param auth_timeout: Set a timeout (in seconds) to wait for an authentication response.
+    :type auth_timeout: float
+
+    :param banner_timeout: Set a timeout to wait for the SSH banner (pass to Paramiko).
+    :type banner_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
+
+    :param default_enter: Character(s) to send to correspond to enter key (default:
+
+

). +:type default_enter: str

+
    :param response_return: Character(s) to use in normalized return data to represent
+            enter key (default:
+
+

) +:type response_return: str

+
    :param fast_cli: Provide a way to optimize for performance. Converts select_delay_factor
+            to select smallest of global and specific. Sets default global_delay_factor to .1
+            (default: False)
+    :type fast_cli: boolean
+
+    :param session_log: File path or BufferedIOBase subclass object to write the session log to.
+    :type session_log: str
+
+    :param session_log_record_writes: The session log generally only records channel reads due
+            to eliminate command duplication due to command echo. You can enable this if you
+            want to record both channel reads and channel writes in the log (default: False).
+    :type session_log_record_writes: boolean
+
+    :param session_log_file_mode: "write" or "append" for session_log file mode
+            (default: "write")
+    :type session_log_file_mode: str
+
+    :param allow_auto_change: Allow automatic configuration changes for terminal settings.
+            (default: False)
+    :type allow_auto_change: bool
+
+    :param encoding: Encoding to be used when writing bytes to the output channel.
+            (default: ascii)
+    :type encoding: str
+
+    :param sock: An open socket or socket-like object (such as a `.Channel`) to use for
+            communication to the target host (default: None).
+    :type sock: socket
+
+    :param global_cmd_verify: Control whether command echo verification is enabled or disabled
+            (default: None). Global attribute takes precedence over function `cmd_verify`
+            argument. Value of `None` indicates to use function `cmd_verify` argument.
+    :type global_cmd_verify: bool|None
+
+
+Source code +
class HuaweiSmartAXSSH(CiscoBaseConnection):
+    """Supports Huawei SmartAX and OLT."""
+
+    def session_preparation(self):
+        """Prepare the session after the connection has been established."""
+        self.ansi_escape_codes = True
+        self._test_channel_read()
+        self.set_base_prompt()
+        self._disable_smart_interaction()
+        self.disable_paging()
+        # Clear the read buffer
+        time.sleep(0.3 * self.global_delay_factor)
+        self.clear_buffer()
+
+    def strip_ansi_escape_codes(self, string_buffer):
+        """
+        Huawei does a strange thing where they add a space and then add ESC[1D
+        to move the cursor to the left one.
+        The extra space is problematic.
+        """
+        code_cursor_left = chr(27) + r"\[\d+D"
+        output = string_buffer
+        pattern = rf" {code_cursor_left}"
+        output = re.sub(pattern, "", output)
+
+        log.debug("Stripping ANSI escape codes")
+        log.debug(f"new_output = {output}")
+        log.debug(f"repr = {repr(output)}")
+        return super().strip_ansi_escape_codes(output)
+
+    def _disable_smart_interaction(self, command="undo smart", delay_factor=1):
+        """Disables the { <cr> } prompt to avoid having to sent a 2nd return after each command"""
+        delay_factor = self.select_delay_factor(delay_factor)
+        time.sleep(delay_factor * 0.1)
+        self.clear_buffer()
+        command = self.normalize_cmd(command)
+        log.debug("In disable_smart_interaction")
+        log.debug(f"Command: {command}")
+        self.write_channel(command)
+        if self.global_cmd_verify is not False:
+            output = self.read_until_pattern(pattern=re.escape(command.strip()))
+        else:
+            output = self.read_until_prompt()
+        log.debug(f"{output}")
+        log.debug("Exiting disable_smart_interaction")
+
+    def disable_paging(self, command="scroll"):
+        return super().disable_paging(command=command)
+
+    def config_mode(self, config_command="config", pattern=""):
+        """Enter configuration mode."""
+        return super().config_mode(config_command=config_command, pattern=pattern)
+
+    def check_config_mode(self, check_string=")#"):
+        return super().check_config_mode(check_string=check_string)
+
+    def exit_config_mode(self, exit_config="return"):
+        return super().exit_config_mode(exit_config=exit_config)
+
+    def check_enable_mode(self, check_string="#"):
+        return super().check_enable_mode(check_string=check_string)
+
+    def enable(self, cmd="enable", pattern="", re_flags=re.IGNORECASE):
+        return super().enable(cmd=cmd, pattern=pattern, re_flags=re_flags)
+
+    def set_base_prompt(self, pri_prompt_terminator=">", alt_prompt_terminator="#"):
+        return super().set_base_prompt(
+            pri_prompt_terminator=pri_prompt_terminator,
+            alt_prompt_terminator=alt_prompt_terminator,
+        )
+
+    def save_config(self, cmd="save", confirm=False, confirm_response=""):
+        """ Save Config for HuaweiSSH"""
+        return super().save_config(
+            cmd=cmd, confirm=confirm, confirm_response=confirm_response
+        )
+
+

Ancestors

+ +

Methods

+
+
+def config_mode(self, config_command='config', pattern='') +
+
+

Enter configuration mode.

+
+Source code +
def config_mode(self, config_command="config", pattern=""):
+    """Enter configuration mode."""
+    return super().config_mode(config_command=config_command, pattern=pattern)
+
+
+
+def save_config(self, cmd='save', confirm=False, confirm_response='') +
+
+

Save Config for HuaweiSSH

+
+Source code +
def save_config(self, cmd="save", confirm=False, confirm_response=""):
+    """ Save Config for HuaweiSSH"""
+    return super().save_config(
+        cmd=cmd, confirm=confirm, confirm_response=confirm_response
+    )
+
+
+
+def session_preparation(self) +
+
+

Prepare the session after the connection has been established.

+
+Source code +
def session_preparation(self):
+    """Prepare the session after the connection has been established."""
+    self.ansi_escape_codes = True
+    self._test_channel_read()
+    self.set_base_prompt()
+    self._disable_smart_interaction()
+    self.disable_paging()
+    # Clear the read buffer
+    time.sleep(0.3 * self.global_delay_factor)
+    self.clear_buffer()
+
+
+
+def strip_ansi_escape_codes(self, string_buffer) +
+
+

Huawei does a strange thing where they add a space and then add ESC[1D +to move the cursor to the left one. +The extra space is problematic.

+
+Source code +
def strip_ansi_escape_codes(self, string_buffer):
+    """
+    Huawei does a strange thing where they add a space and then add ESC[1D
+    to move the cursor to the left one.
+    The extra space is problematic.
+    """
+    code_cursor_left = chr(27) + r"\[\d+D"
+    output = string_buffer
+    pattern = rf" {code_cursor_left}"
+    output = re.sub(pattern, "", output)
+
+    log.debug("Stripping ANSI escape codes")
+    log.debug(f"new_output = {output}")
+    log.debug(f"repr = {repr(output)}")
+    return super().strip_ansi_escape_codes(output)
+
+
+
+

Inherited members

+ +
class HuaweiTelnet (ip='', host='', username='', password=None, secret='', port=None, device_type='', verbose=False, global_delay_factor=1, global_cmd_verify=None, use_keys=False, key_file=None, pkey=None, passphrase=None, allow_agent=False, ssh_strict=False, system_host_keys=False, alt_host_keys=False, alt_key_file='', ssh_config_file=None, timeout=100, session_timeout=60, auth_timeout=None, blocking_timeout=20, banner_timeout=15, keepalive=0, default_enter=None, response_return=None, serial_settings=None, fast_cli=False, session_log=None, session_log_record_writes=False, session_log_file_mode='write', allow_auto_change=False, encoding='ascii', sock=None) @@ -922,6 +1263,7 @@

Index

  • Sub-modules

  • Classes

    @@ -933,6 +1275,15 @@

    H

  • +

    HuaweiSmartAXSSH

    + +
  • +
  • HuaweiTelnet

    • telnet_login
    • diff --git a/docs/netmiko/index.html b/docs/netmiko/index.html index 36984bf0c..54220c5da 100644 --- a/docs/netmiko/index.html +++ b/docs/netmiko/index.html @@ -47,7 +47,7 @@

      Module netmiko

      # Alternate naming Netmiko = ConnectHandler -__version__ = "3.0.0" +__version__ = "3.1.1" __all__ = ( "ConnectHandler", "ssh_dispatcher", @@ -361,7 +361,7 @@

      Functions

      -def file_transfer(ssh_conn, source_file, dest_file, file_system=None, direction='put', disable_md5=False, inline_transfer=False, overwrite_file=False, socket_timeout=10.0) +def file_transfer(ssh_conn, source_file, dest_file, file_system=None, direction='put', disable_md5=False, inline_transfer=False, overwrite_file=False, socket_timeout=10.0, verify_file=None)

      Use Secure Copy or Inline (IOS-only) to transfer files to/from network devices.

      @@ -383,6 +383,7 @@

      Functions

      inline_transfer=False, overwrite_file=False, socket_timeout=10.0, + verify_file=None, ): """Use Secure Copy or Inline (IOS-only) to transfer files to/from network devices. @@ -417,6 +418,10 @@

      Functions

      if not cisco_ios and inline_transfer: raise ValueError("Inline Transfer only supported for Cisco IOS/Cisco IOS-XE") + # Replace disable_md5 argument with verify_file argument across time + if verify_file is None: + verify_file = not disable_md5 + scp_args = { "ssh_conn": ssh_conn, "source_file": source_file, @@ -432,13 +437,13 @@

      Functions

      with TransferClass(**scp_args) as scp_transfer: if scp_transfer.check_file_exists(): if overwrite_file: - if not disable_md5: - if scp_transfer.compare_md5(): + if verify_file: + if scp_transfer.verify_file(): return nottransferred_but_verified else: # File exists, you can overwrite it, MD5 is wrong (transfer file) verifyspace_and_transferfile(scp_transfer) - if scp_transfer.compare_md5(): + if scp_transfer.verify_file(): return transferred_and_verified else: raise ValueError( @@ -450,16 +455,16 @@

      Functions

      return transferred_and_notverified else: # File exists, but you can't overwrite it. - if not disable_md5: - if scp_transfer.compare_md5(): + if verify_file: + if scp_transfer.verify_file(): return nottransferred_but_verified msg = "File already exists and overwrite_file is disabled" raise ValueError(msg) else: verifyspace_and_transferfile(scp_transfer) # File doesn't exist - if not disable_md5: - if scp_transfer.compare_md5(): + if verify_file: + if scp_transfer.verify_file(): return transferred_and_verified else: raise ValueError("MD5 failure between source and destination files") @@ -1477,18 +1482,16 @@

      Classes

      output = self.strip_prompt(output) return output - def establish_connection(self, width=None, height=None): + def establish_connection(self, width=511, height=1000): """Establish SSH connection to the network device Timeout will generate a NetmikoTimeoutException Authentication failure will generate a NetmikoAuthenticationException - width and height are needed for Fortinet paging setting. - - :param width: Specified width of the VT100 terminal window + :param width: Specified width of the VT100 terminal window (default: 511) :type width: int - :param height: Specified height of the VT100 terminal window + :param height: Specified height of the VT100 terminal window (default: 1000) :type height: int """ if self.protocol == "telnet": @@ -1524,12 +1527,9 @@

      Classes

      print(f"SSH connection established to {self.host}:{self.port}") # Use invoke_shell to establish an 'interactive session' - if width and height: - self.remote_conn = self.remote_conn_pre.invoke_shell( - term="vt100", width=width, height=height - ) - else: - self.remote_conn = self.remote_conn_pre.invoke_shell() + self.remote_conn = self.remote_conn_pre.invoke_shell( + term="vt100", width=width, height=height + ) self.remote_conn.settimeout(self.blocking_timeout) if self.keepalive: @@ -1635,7 +1635,10 @@

      Classes

      log.debug(f"Command: {command}") self.write_channel(command) # Make sure you read until you detect the command echo (avoid getting out of sync) - output = self.read_until_pattern(pattern=re.escape(command.strip())) + if self.global_cmd_verify is not False: + output = self.read_until_pattern(pattern=re.escape(command.strip())) + else: + output = self.read_until_prompt() log.debug(f"{output}") log.debug("Exiting disable_paging") return output @@ -1658,7 +1661,10 @@

      Classes

      command = self.normalize_cmd(command) self.write_channel(command) # Make sure you read until you detect the command echo (avoid getting out of sync) - output = self.read_until_pattern(pattern=re.escape(command.strip())) + if self.global_cmd_verify is not False: + output = self.read_until_pattern(pattern=re.escape(command.strip())) + else: + output = self.read_until_prompt() return output def set_base_prompt( @@ -2225,7 +2231,10 @@

      Classes

      if not self.check_config_mode(): self.write_channel(self.normalize_cmd(config_command)) # Make sure you read until you detect the command echo (avoid getting out of sync) - output += self.read_until_pattern(pattern=re.escape(config_command.strip())) + if self.global_cmd_verify is not False: + output += self.read_until_pattern( + pattern=re.escape(config_command.strip()) + ) if not re.search(pattern, output, flags=re.M): output += self.read_until_pattern(pattern=pattern) if not self.check_config_mode(): @@ -2245,7 +2254,10 @@

      Classes

      if self.check_config_mode(): self.write_channel(self.normalize_cmd(exit_config)) # Make sure you read until you detect the command echo (avoid getting out of sync) - output += self.read_until_pattern(pattern=re.escape(exit_config.strip())) + if self.global_cmd_verify is not False: + output += self.read_until_pattern( + pattern=re.escape(exit_config.strip()) + ) if not re.search(pattern, output, flags=re.M): output += self.read_until_pattern(pattern=pattern) if self.check_config_mode(): @@ -2696,7 +2708,10 @@

      Methods

      if not self.check_config_mode(): self.write_channel(self.normalize_cmd(config_command)) # Make sure you read until you detect the command echo (avoid getting out of sync) - output += self.read_until_pattern(pattern=re.escape(config_command.strip())) + if self.global_cmd_verify is not False: + output += self.read_until_pattern( + pattern=re.escape(config_command.strip()) + ) if not re.search(pattern, output, flags=re.M): output += self.read_until_pattern(pattern=pattern) if not self.check_config_mode(): @@ -2732,7 +2747,10 @@

      Methods

      log.debug(f"Command: {command}") self.write_channel(command) # Make sure you read until you detect the command echo (avoid getting out of sync) - output = self.read_until_pattern(pattern=re.escape(command.strip())) + if self.global_cmd_verify is not False: + output = self.read_until_pattern(pattern=re.escape(command.strip())) + else: + output = self.read_until_prompt() log.debug(f"{output}") log.debug("Exiting disable_paging") return output
  • @@ -2810,31 +2828,28 @@

    Methods

    -def establish_connection(self, width=None, height=None) +def establish_connection(self, width=511, height=1000)

    Establish SSH connection to the network device

    Timeout will generate a NetmikoTimeoutException Authentication failure will generate a NetmikoAuthenticationException

    -

    width and height are needed for Fortinet paging setting.

    -

    :param width: Specified width of the VT100 terminal window +

    :param width: Specified width of the VT100 terminal window (default: 511) :type width: int

    -

    :param height: Specified height of the VT100 terminal window +

    :param height: Specified height of the VT100 terminal window (default: 1000) :type height: int

    Source code -
    def establish_connection(self, width=None, height=None):
    +
    def establish_connection(self, width=511, height=1000):
         """Establish SSH connection to the network device
     
         Timeout will generate a NetmikoTimeoutException
         Authentication failure will generate a NetmikoAuthenticationException
     
    -    width and height are needed for Fortinet paging setting.
    -
    -    :param width: Specified width of the VT100 terminal window
    +    :param width: Specified width of the VT100 terminal window (default: 511)
         :type width: int
     
    -    :param height: Specified height of the VT100 terminal window
    +    :param height: Specified height of the VT100 terminal window (default: 1000)
         :type height: int
         """
         if self.protocol == "telnet":
    @@ -2870,12 +2885,9 @@ 

    Methods

    print(f"SSH connection established to {self.host}:{self.port}") # Use invoke_shell to establish an 'interactive session' - if width and height: - self.remote_conn = self.remote_conn_pre.invoke_shell( - term="vt100", width=width, height=height - ) - else: - self.remote_conn = self.remote_conn_pre.invoke_shell() + self.remote_conn = self.remote_conn_pre.invoke_shell( + term="vt100", width=width, height=height + ) self.remote_conn.settimeout(self.blocking_timeout) if self.keepalive: @@ -2910,7 +2922,10 @@

    Methods

    if self.check_config_mode(): self.write_channel(self.normalize_cmd(exit_config)) # Make sure you read until you detect the command echo (avoid getting out of sync) - output += self.read_until_pattern(pattern=re.escape(exit_config.strip())) + if self.global_cmd_verify is not False: + output += self.read_until_pattern( + pattern=re.escape(exit_config.strip()) + ) if not re.search(pattern, output, flags=re.M): output += self.read_until_pattern(pattern=pattern) if self.check_config_mode(): @@ -3909,7 +3924,10 @@

    Methods

    command = self.normalize_cmd(command) self.write_channel(command) # Make sure you read until you detect the command echo (avoid getting out of sync) - output = self.read_until_pattern(pattern=re.escape(command.strip())) + if self.global_cmd_verify is not False: + output = self.read_until_pattern(pattern=re.escape(command.strip())) + else: + output = self.read_until_prompt() return output
    @@ -4834,7 +4852,45 @@

    Methods

    else: return cached_results - def _autodetect_std(self, cmd="", search_patterns=None, re_flags=re.I, priority=99): + def _autodetect_remote_version( + self, search_patterns=None, re_flags=re.IGNORECASE, priority=99 + ): + """ + Method to try auto-detect the device type, by matching a regular expression on the reported + remote version of the SSH server. + + Parameters + ---------- + search_patterns : list + A list of regular expression to look for in the reported remote SSH version + (default: None). + re_flags: re.flags, optional + Any flags from the python re module to modify the regular expression (default: re.I). + priority: int, optional + The confidence the match is right between 0 and 99 (default: 99). + """ + invalid_responses = [r"^$"] + + if not search_patterns: + return 0 + + try: + remote_version = self.connection.remote_conn.transport.remote_version + for pattern in invalid_responses: + match = re.search(pattern, remote_version, flags=re.I) + if match: + return 0 + for pattern in search_patterns: + match = re.search(pattern, remote_version, flags=re_flags) + if match: + return priority + except Exception: + return 0 + return 0 + + def _autodetect_std( + self, cmd="", search_patterns=None, re_flags=re.IGNORECASE, priority=99 + ): """ Standard method to try to auto-detect the device type. This method will be called for each device_type present in SSH_MAPPER_BASE dict ('dispatch' key). It will attempt to send a diff --git a/docs/netmiko/nokia/index.html b/docs/netmiko/nokia/index.html index 2043abd0d..fc69b2878 100644 --- a/docs/netmiko/nokia/index.html +++ b/docs/netmiko/nokia/index.html @@ -22,9 +22,9 @@

    Module netmiko.nokia

    Source code -
    from netmiko.nokia.nokia_sros_ssh import NokiaSrosSSH
    +
    from netmiko.nokia.nokia_sros_ssh import NokiaSrosSSH, NokiaSrosFileTransfer
     
    -__all__ = ["NokiaSrosSSH"]
    +__all__ = ["NokiaSrosSSH", "NokiaSrosFileTransfer"]
    @@ -43,6 +43,189 @@

    Sub-modules

    Classes

    +
    +class NokiaSrosFileTransfer +(ssh_conn, source_file, dest_file, hash_supported=False, **kwargs) +
    +
    +

    Class to manage SCP file transfer and associated SSH control channel.

    +
    +Source code +
    class NokiaSrosFileTransfer(BaseFileTransfer):
    +    def __init__(
    +        self, ssh_conn, source_file, dest_file, hash_supported=False, **kwargs
    +    ):
    +        super().__init__(
    +            ssh_conn, source_file, dest_file, hash_supported=hash_supported, **kwargs
    +        )
    +
    +    def _file_cmd_prefix(self):
    +        """
    +        Allow MD-CLI to execute file operations by using classical CLI.
    +
    +        Returns "//" if the current prompt is MD-CLI (empty string otherwise).
    +        """
    +        return "//" if "@" in self.ssh_ctl_chan.base_prompt else ""
    +
    +    def remote_space_available(self, search_pattern=r"(\d+)\s+\w+\s+free"):
    +        """Return space available on remote device."""
    +
    +        # Sample text for search_pattern.
    +        # "               3 Dir(s)               961531904 bytes free."
    +        remote_cmd = self._file_cmd_prefix() + "file dir {}".format(self.file_system)
    +        remote_output = self.ssh_ctl_chan.send_command(remote_cmd)
    +        match = re.search(search_pattern, remote_output)
    +        return int(match.group(1))
    +
    +    def check_file_exists(self, remote_cmd=""):
    +        """Check if destination file exists (returns boolean)."""
    +
    +        if self.direction == "put":
    +            if not remote_cmd:
    +                remote_cmd = self._file_cmd_prefix() + "file dir {}/{}".format(
    +                    self.file_system, self.dest_file
    +                )
    +            dest_file_name = self.dest_file.replace("\\", "/").split("/")[-1]
    +            remote_out = self.ssh_ctl_chan.send_command(remote_cmd)
    +            if "File Not Found" in remote_out:
    +                return False
    +            elif dest_file_name in remote_out:
    +                return True
    +            else:
    +                raise ValueError("Unexpected output from check_file_exists")
    +        elif self.direction == "get":
    +            return os.path.exists(self.dest_file)
    +
    +    def remote_file_size(self, remote_cmd=None, remote_file=None):
    +        """Get the file size of the remote file."""
    +
    +        if remote_file is None:
    +            if self.direction == "put":
    +                remote_file = self.dest_file
    +            elif self.direction == "get":
    +                remote_file = self.source_file
    +        if not remote_cmd:
    +            remote_cmd = self._file_cmd_prefix() + "file dir {}/{}".format(
    +                self.file_system, remote_file
    +            )
    +        remote_out = self.ssh_ctl_chan.send_command(remote_cmd)
    +
    +        if "File Not Found" in remote_out:
    +            raise IOError("Unable to find file on remote system")
    +
    +        # Parse dir output for filename. Output format is:
    +        # "10/16/2019  10:00p                6738 {filename}"
    +
    +        pattern = r"\S+\s+\S+\s+(\d+)\s+{}".format(re.escape(remote_file))
    +        match = re.search(pattern, remote_out)
    +
    +        if not match:
    +            raise ValueError("Filename entry not found in dir output")
    +
    +        file_size = int(match.group(1))
    +        return file_size
    +
    +    def verify_file(self):
    +        """Verify the file has been transferred correctly based on filesize."""
    +        if self.direction == "put":
    +            return os.stat(self.source_file).st_size == self.remote_file_size(
    +                remote_file=self.dest_file
    +            )
    +        elif self.direction == "get":
    +            return (
    +                self.remote_file_size(remote_file=self.source_file)
    +                == os.stat(self.dest_file).st_size
    +            )
    +
    +    def file_md5(self, **kwargs):
    +        raise AttributeError("SR-OS does not support an MD5-hash operation.")
    +
    +    def process_md5(self, **kwargs):
    +        raise AttributeError("SR-OS does not support an MD5-hash operation.")
    +
    +    def compare_md5(self, **kwargs):
    +        raise AttributeError("SR-OS does not support an MD5-hash operation.")
    +
    +    def remote_md5(self, **kwargs):
    +        raise AttributeError("SR-OS does not support an MD5-hash operation.")
    +
    +

    Ancestors

    + +

    Methods

    +
    +
    +def check_file_exists(self, remote_cmd='') +
    +
    +

    Check if destination file exists (returns boolean).

    +
    +Source code +
    def check_file_exists(self, remote_cmd=""):
    +    """Check if destination file exists (returns boolean)."""
    +
    +    if self.direction == "put":
    +        if not remote_cmd:
    +            remote_cmd = self._file_cmd_prefix() + "file dir {}/{}".format(
    +                self.file_system, self.dest_file
    +            )
    +        dest_file_name = self.dest_file.replace("\\", "/").split("/")[-1]
    +        remote_out = self.ssh_ctl_chan.send_command(remote_cmd)
    +        if "File Not Found" in remote_out:
    +            return False
    +        elif dest_file_name in remote_out:
    +            return True
    +        else:
    +            raise ValueError("Unexpected output from check_file_exists")
    +    elif self.direction == "get":
    +        return os.path.exists(self.dest_file)
    +
    +
    +
    +def verify_file(self) +
    +
    +

    Verify the file has been transferred correctly based on filesize.

    +
    +Source code +
    def verify_file(self):
    +    """Verify the file has been transferred correctly based on filesize."""
    +    if self.direction == "put":
    +        return os.stat(self.source_file).st_size == self.remote_file_size(
    +            remote_file=self.dest_file
    +        )
    +    elif self.direction == "get":
    +        return (
    +            self.remote_file_size(remote_file=self.source_file)
    +            == os.stat(self.dest_file).st_size
    +        )
    +
    +
    +
    +

    Inherited members

    + +
    class NokiaSrosSSH (ip='', host='', username='', password=None, secret='', port=None, device_type='', verbose=False, global_delay_factor=1, global_cmd_verify=None, use_keys=False, key_file=None, pkey=None, passphrase=None, allow_agent=False, ssh_strict=False, system_host_keys=False, alt_host_keys=False, alt_key_file='', ssh_config_file=None, timeout=100, session_timeout=60, auth_timeout=None, blocking_timeout=20, banner_timeout=15, keepalive=0, default_enter=None, response_return=None, serial_settings=None, fast_cli=False, session_log=None, session_log_record_writes=False, session_log_file_mode='write', allow_auto_change=False, encoding='ascii', sock=None) @@ -216,6 +399,8 @@

    Classes

    # "@" indicates model-driven CLI (vs Classical CLI) if "@" in self.base_prompt: self.disable_paging(command="environment more false") + # To perform file operations we need to disable paging in classical-CLI also + self.disable_paging(command="//environment no more") self.set_terminal_width(command="environment console width 512") else: self.disable_paging(command="environment no more") @@ -266,7 +451,10 @@

    Classes

    output += self._discard() cmd = "quit-config" self.write_channel(self.normalize_cmd(cmd)) - output += self.read_until_pattern(pattern=re.escape(cmd)) + if self.global_cmd_verify is not False: + output += self.read_until_pattern(pattern=re.escape(cmd)) + else: + output += self.read_until_prompt() if self.check_config_mode(): raise ValueError("Failed to exit configuration mode") return output @@ -301,16 +489,25 @@

    Classes

    log.info("Apply uncommitted changes!") cmd = "commit" self.write_channel(self.normalize_cmd(cmd)) - output += self.read_until_pattern(pattern=re.escape(cmd)) - output += self.read_until_pattern(r"@") + new_output = "" + if self.global_cmd_verify is not False: + new_output += self.read_until_pattern(pattern=re.escape(cmd)) + if "@" not in new_output: + new_output += self.read_until_pattern(r"@") + output += new_output return output def _exit_all(self): """Return to the 'root' context.""" + output = "" exit_cmd = "exit all" self.write_channel(self.normalize_cmd(exit_cmd)) # Make sure you read until you detect the command echo (avoid getting out of sync) - return self.read_until_pattern(pattern=re.escape(exit_cmd)) + if self.global_cmd_verify is not False: + output += self.read_until_pattern(pattern=re.escape(exit_cmd)) + else: + output += self.read_until_prompt() + return output def _discard(self): """Discard changes from private candidate for Nokia SR OS""" @@ -318,7 +515,9 @@

    Classes

    if "@" in self.base_prompt: cmd = "discard" self.write_channel(self.normalize_cmd(cmd)) - new_output = self.read_until_pattern(pattern=re.escape(cmd)) + new_output = "" + if self.global_cmd_verify is not False: + new_output += self.read_until_pattern(pattern=re.escape(cmd)) if "@" not in new_output: new_output += self.read_until_prompt() output += new_output @@ -415,8 +614,12 @@

    Methods

    log.info("Apply uncommitted changes!") cmd = "commit" self.write_channel(self.normalize_cmd(cmd)) - output += self.read_until_pattern(pattern=re.escape(cmd)) - output += self.read_until_pattern(r"@") + new_output = "" + if self.global_cmd_verify is not False: + new_output += self.read_until_pattern(pattern=re.escape(cmd)) + if "@" not in new_output: + new_output += self.read_until_pattern(r"@") + output += new_output return output
    @@ -468,7 +671,10 @@

    Methods

    output += self._discard() cmd = "quit-config" self.write_channel(self.normalize_cmd(cmd)) - output += self.read_until_pattern(pattern=re.escape(cmd)) + if self.global_cmd_verify is not False: + output += self.read_until_pattern(pattern=re.escape(cmd)) + else: + output += self.read_until_prompt() if self.check_config_mode(): raise ValueError("Failed to exit configuration mode") return output
    @@ -610,6 +816,13 @@

    Index

  • Classes

    • +

      NokiaSrosFileTransfer

      + +
    • +
    • NokiaSrosSSH

      • check_config_mode
      • diff --git a/docs/netmiko/nokia/nokia_sros_ssh.html b/docs/netmiko/nokia/nokia_sros_ssh.html index 34884b081..26e7604a8 100644 --- a/docs/netmiko/nokia/nokia_sros_ssh.html +++ b/docs/netmiko/nokia/nokia_sros_ssh.html @@ -31,10 +31,12 @@

        Module netmiko.nokia.nokia_sros_ssh

        # https://github.com/ktbyers/netmiko/blob/develop/LICENSE import re +import os import time from netmiko import log from netmiko.base_connection import BaseConnection +from netmiko.scp_handler import BaseFileTransfer class NokiaSrosSSH(BaseConnection): @@ -63,6 +65,8 @@

        Module netmiko.nokia.nokia_sros_ssh

        # "@" indicates model-driven CLI (vs Classical CLI) if "@" in self.base_prompt: self.disable_paging(command="environment more false") + # To perform file operations we need to disable paging in classical-CLI also + self.disable_paging(command="//environment no more") self.set_terminal_width(command="environment console width 512") else: self.disable_paging(command="environment no more") @@ -113,7 +117,10 @@

        Module netmiko.nokia.nokia_sros_ssh

        output += self._discard() cmd = "quit-config" self.write_channel(self.normalize_cmd(cmd)) - output += self.read_until_pattern(pattern=re.escape(cmd)) + if self.global_cmd_verify is not False: + output += self.read_until_pattern(pattern=re.escape(cmd)) + else: + output += self.read_until_prompt() if self.check_config_mode(): raise ValueError("Failed to exit configuration mode") return output @@ -148,16 +155,25 @@

        Module netmiko.nokia.nokia_sros_ssh

        log.info("Apply uncommitted changes!") cmd = "commit" self.write_channel(self.normalize_cmd(cmd)) - output += self.read_until_pattern(pattern=re.escape(cmd)) - output += self.read_until_pattern(r"@") + new_output = "" + if self.global_cmd_verify is not False: + new_output += self.read_until_pattern(pattern=re.escape(cmd)) + if "@" not in new_output: + new_output += self.read_until_pattern(r"@") + output += new_output return output def _exit_all(self): """Return to the 'root' context.""" + output = "" exit_cmd = "exit all" self.write_channel(self.normalize_cmd(exit_cmd)) # Make sure you read until you detect the command echo (avoid getting out of sync) - return self.read_until_pattern(pattern=re.escape(exit_cmd)) + if self.global_cmd_verify is not False: + output += self.read_until_pattern(pattern=re.escape(exit_cmd)) + else: + output += self.read_until_prompt() + return output def _discard(self): """Discard changes from private candidate for Nokia SR OS""" @@ -165,7 +181,9 @@

        Module netmiko.nokia.nokia_sros_ssh

        if "@" in self.base_prompt: cmd = "discard" self.write_channel(self.normalize_cmd(cmd)) - new_output = self.read_until_pattern(pattern=re.escape(cmd)) + new_output = "" + if self.global_cmd_verify is not False: + new_output += self.read_until_pattern(pattern=re.escape(cmd)) if "@" not in new_output: new_output += self.read_until_prompt() output += new_output @@ -191,7 +209,106 @@

        Module netmiko.nokia.nokia_sros_ssh

        pass # Always try to send final 'logout'. self._session_log_fin = True - self.write_channel(command + self.RETURN) + self.write_channel(command + self.RETURN) + + +class NokiaSrosFileTransfer(BaseFileTransfer): + def __init__( + self, ssh_conn, source_file, dest_file, hash_supported=False, **kwargs + ): + super().__init__( + ssh_conn, source_file, dest_file, hash_supported=hash_supported, **kwargs + ) + + def _file_cmd_prefix(self): + """ + Allow MD-CLI to execute file operations by using classical CLI. + + Returns "//" if the current prompt is MD-CLI (empty string otherwise). + """ + return "//" if "@" in self.ssh_ctl_chan.base_prompt else "" + + def remote_space_available(self, search_pattern=r"(\d+)\s+\w+\s+free"): + """Return space available on remote device.""" + + # Sample text for search_pattern. + # " 3 Dir(s) 961531904 bytes free." + remote_cmd = self._file_cmd_prefix() + "file dir {}".format(self.file_system) + remote_output = self.ssh_ctl_chan.send_command(remote_cmd) + match = re.search(search_pattern, remote_output) + return int(match.group(1)) + + def check_file_exists(self, remote_cmd=""): + """Check if destination file exists (returns boolean).""" + + if self.direction == "put": + if not remote_cmd: + remote_cmd = self._file_cmd_prefix() + "file dir {}/{}".format( + self.file_system, self.dest_file + ) + dest_file_name = self.dest_file.replace("\\", "/").split("/")[-1] + remote_out = self.ssh_ctl_chan.send_command(remote_cmd) + if "File Not Found" in remote_out: + return False + elif dest_file_name in remote_out: + return True + else: + raise ValueError("Unexpected output from check_file_exists") + elif self.direction == "get": + return os.path.exists(self.dest_file) + + def remote_file_size(self, remote_cmd=None, remote_file=None): + """Get the file size of the remote file.""" + + if remote_file is None: + if self.direction == "put": + remote_file = self.dest_file + elif self.direction == "get": + remote_file = self.source_file + if not remote_cmd: + remote_cmd = self._file_cmd_prefix() + "file dir {}/{}".format( + self.file_system, remote_file + ) + remote_out = self.ssh_ctl_chan.send_command(remote_cmd) + + if "File Not Found" in remote_out: + raise IOError("Unable to find file on remote system") + + # Parse dir output for filename. Output format is: + # "10/16/2019 10:00p 6738 {filename}" + + pattern = r"\S+\s+\S+\s+(\d+)\s+{}".format(re.escape(remote_file)) + match = re.search(pattern, remote_out) + + if not match: + raise ValueError("Filename entry not found in dir output") + + file_size = int(match.group(1)) + return file_size + + def verify_file(self): + """Verify the file has been transferred correctly based on filesize.""" + if self.direction == "put": + return os.stat(self.source_file).st_size == self.remote_file_size( + remote_file=self.dest_file + ) + elif self.direction == "get": + return ( + self.remote_file_size(remote_file=self.source_file) + == os.stat(self.dest_file).st_size + ) + + def file_md5(self, **kwargs): + raise AttributeError("SR-OS does not support an MD5-hash operation.") + + def process_md5(self, **kwargs): + raise AttributeError("SR-OS does not support an MD5-hash operation.") + + def compare_md5(self, **kwargs): + raise AttributeError("SR-OS does not support an MD5-hash operation.") + + def remote_md5(self, **kwargs): + raise AttributeError("SR-OS does not support an MD5-hash operation.")
  • @@ -203,6 +320,189 @@

    Module netmiko.nokia.nokia_sros_ssh

    Classes

    +
    +class NokiaSrosFileTransfer +(ssh_conn, source_file, dest_file, hash_supported=False, **kwargs) +
    +
    +

    Class to manage SCP file transfer and associated SSH control channel.

    +
    +Source code +
    class NokiaSrosFileTransfer(BaseFileTransfer):
    +    def __init__(
    +        self, ssh_conn, source_file, dest_file, hash_supported=False, **kwargs
    +    ):
    +        super().__init__(
    +            ssh_conn, source_file, dest_file, hash_supported=hash_supported, **kwargs
    +        )
    +
    +    def _file_cmd_prefix(self):
    +        """
    +        Allow MD-CLI to execute file operations by using classical CLI.
    +
    +        Returns "//" if the current prompt is MD-CLI (empty string otherwise).
    +        """
    +        return "//" if "@" in self.ssh_ctl_chan.base_prompt else ""
    +
    +    def remote_space_available(self, search_pattern=r"(\d+)\s+\w+\s+free"):
    +        """Return space available on remote device."""
    +
    +        # Sample text for search_pattern.
    +        # "               3 Dir(s)               961531904 bytes free."
    +        remote_cmd = self._file_cmd_prefix() + "file dir {}".format(self.file_system)
    +        remote_output = self.ssh_ctl_chan.send_command(remote_cmd)
    +        match = re.search(search_pattern, remote_output)
    +        return int(match.group(1))
    +
    +    def check_file_exists(self, remote_cmd=""):
    +        """Check if destination file exists (returns boolean)."""
    +
    +        if self.direction == "put":
    +            if not remote_cmd:
    +                remote_cmd = self._file_cmd_prefix() + "file dir {}/{}".format(
    +                    self.file_system, self.dest_file
    +                )
    +            dest_file_name = self.dest_file.replace("\\", "/").split("/")[-1]
    +            remote_out = self.ssh_ctl_chan.send_command(remote_cmd)
    +            if "File Not Found" in remote_out:
    +                return False
    +            elif dest_file_name in remote_out:
    +                return True
    +            else:
    +                raise ValueError("Unexpected output from check_file_exists")
    +        elif self.direction == "get":
    +            return os.path.exists(self.dest_file)
    +
    +    def remote_file_size(self, remote_cmd=None, remote_file=None):
    +        """Get the file size of the remote file."""
    +
    +        if remote_file is None:
    +            if self.direction == "put":
    +                remote_file = self.dest_file
    +            elif self.direction == "get":
    +                remote_file = self.source_file
    +        if not remote_cmd:
    +            remote_cmd = self._file_cmd_prefix() + "file dir {}/{}".format(
    +                self.file_system, remote_file
    +            )
    +        remote_out = self.ssh_ctl_chan.send_command(remote_cmd)
    +
    +        if "File Not Found" in remote_out:
    +            raise IOError("Unable to find file on remote system")
    +
    +        # Parse dir output for filename. Output format is:
    +        # "10/16/2019  10:00p                6738 {filename}"
    +
    +        pattern = r"\S+\s+\S+\s+(\d+)\s+{}".format(re.escape(remote_file))
    +        match = re.search(pattern, remote_out)
    +
    +        if not match:
    +            raise ValueError("Filename entry not found in dir output")
    +
    +        file_size = int(match.group(1))
    +        return file_size
    +
    +    def verify_file(self):
    +        """Verify the file has been transferred correctly based on filesize."""
    +        if self.direction == "put":
    +            return os.stat(self.source_file).st_size == self.remote_file_size(
    +                remote_file=self.dest_file
    +            )
    +        elif self.direction == "get":
    +            return (
    +                self.remote_file_size(remote_file=self.source_file)
    +                == os.stat(self.dest_file).st_size
    +            )
    +
    +    def file_md5(self, **kwargs):
    +        raise AttributeError("SR-OS does not support an MD5-hash operation.")
    +
    +    def process_md5(self, **kwargs):
    +        raise AttributeError("SR-OS does not support an MD5-hash operation.")
    +
    +    def compare_md5(self, **kwargs):
    +        raise AttributeError("SR-OS does not support an MD5-hash operation.")
    +
    +    def remote_md5(self, **kwargs):
    +        raise AttributeError("SR-OS does not support an MD5-hash operation.")
    +
    +

    Ancestors

    + +

    Methods

    +
    +
    +def check_file_exists(self, remote_cmd='') +
    +
    +

    Check if destination file exists (returns boolean).

    +
    +Source code +
    def check_file_exists(self, remote_cmd=""):
    +    """Check if destination file exists (returns boolean)."""
    +
    +    if self.direction == "put":
    +        if not remote_cmd:
    +            remote_cmd = self._file_cmd_prefix() + "file dir {}/{}".format(
    +                self.file_system, self.dest_file
    +            )
    +        dest_file_name = self.dest_file.replace("\\", "/").split("/")[-1]
    +        remote_out = self.ssh_ctl_chan.send_command(remote_cmd)
    +        if "File Not Found" in remote_out:
    +            return False
    +        elif dest_file_name in remote_out:
    +            return True
    +        else:
    +            raise ValueError("Unexpected output from check_file_exists")
    +    elif self.direction == "get":
    +        return os.path.exists(self.dest_file)
    +
    +
    +
    +def verify_file(self) +
    +
    +

    Verify the file has been transferred correctly based on filesize.

    +
    +Source code +
    def verify_file(self):
    +    """Verify the file has been transferred correctly based on filesize."""
    +    if self.direction == "put":
    +        return os.stat(self.source_file).st_size == self.remote_file_size(
    +            remote_file=self.dest_file
    +        )
    +    elif self.direction == "get":
    +        return (
    +            self.remote_file_size(remote_file=self.source_file)
    +            == os.stat(self.dest_file).st_size
    +        )
    +
    +
    +
    +

    Inherited members

    + +
    class NokiaSrosSSH (ip='', host='', username='', password=None, secret='', port=None, device_type='', verbose=False, global_delay_factor=1, global_cmd_verify=None, use_keys=False, key_file=None, pkey=None, passphrase=None, allow_agent=False, ssh_strict=False, system_host_keys=False, alt_host_keys=False, alt_key_file='', ssh_config_file=None, timeout=100, session_timeout=60, auth_timeout=None, blocking_timeout=20, banner_timeout=15, keepalive=0, default_enter=None, response_return=None, serial_settings=None, fast_cli=False, session_log=None, session_log_record_writes=False, session_log_file_mode='write', allow_auto_change=False, encoding='ascii', sock=None) @@ -376,6 +676,8 @@

    Classes

    # "@" indicates model-driven CLI (vs Classical CLI) if "@" in self.base_prompt: self.disable_paging(command="environment more false") + # To perform file operations we need to disable paging in classical-CLI also + self.disable_paging(command="//environment no more") self.set_terminal_width(command="environment console width 512") else: self.disable_paging(command="environment no more") @@ -426,7 +728,10 @@

    Classes

    output += self._discard() cmd = "quit-config" self.write_channel(self.normalize_cmd(cmd)) - output += self.read_until_pattern(pattern=re.escape(cmd)) + if self.global_cmd_verify is not False: + output += self.read_until_pattern(pattern=re.escape(cmd)) + else: + output += self.read_until_prompt() if self.check_config_mode(): raise ValueError("Failed to exit configuration mode") return output @@ -461,16 +766,25 @@

    Classes

    log.info("Apply uncommitted changes!") cmd = "commit" self.write_channel(self.normalize_cmd(cmd)) - output += self.read_until_pattern(pattern=re.escape(cmd)) - output += self.read_until_pattern(r"@") + new_output = "" + if self.global_cmd_verify is not False: + new_output += self.read_until_pattern(pattern=re.escape(cmd)) + if "@" not in new_output: + new_output += self.read_until_pattern(r"@") + output += new_output return output def _exit_all(self): """Return to the 'root' context.""" + output = "" exit_cmd = "exit all" self.write_channel(self.normalize_cmd(exit_cmd)) # Make sure you read until you detect the command echo (avoid getting out of sync) - return self.read_until_pattern(pattern=re.escape(exit_cmd)) + if self.global_cmd_verify is not False: + output += self.read_until_pattern(pattern=re.escape(exit_cmd)) + else: + output += self.read_until_prompt() + return output def _discard(self): """Discard changes from private candidate for Nokia SR OS""" @@ -478,7 +792,9 @@

    Classes

    if "@" in self.base_prompt: cmd = "discard" self.write_channel(self.normalize_cmd(cmd)) - new_output = self.read_until_pattern(pattern=re.escape(cmd)) + new_output = "" + if self.global_cmd_verify is not False: + new_output += self.read_until_pattern(pattern=re.escape(cmd)) if "@" not in new_output: new_output += self.read_until_prompt() output += new_output @@ -575,8 +891,12 @@

    Methods

    log.info("Apply uncommitted changes!") cmd = "commit" self.write_channel(self.normalize_cmd(cmd)) - output += self.read_until_pattern(pattern=re.escape(cmd)) - output += self.read_until_pattern(r"@") + new_output = "" + if self.global_cmd_verify is not False: + new_output += self.read_until_pattern(pattern=re.escape(cmd)) + if "@" not in new_output: + new_output += self.read_until_pattern(r"@") + output += new_output return output
    @@ -628,7 +948,10 @@

    Methods

    output += self._discard() cmd = "quit-config" self.write_channel(self.normalize_cmd(cmd)) - output += self.read_until_pattern(pattern=re.escape(cmd)) + if self.global_cmd_verify is not False: + output += self.read_until_pattern(pattern=re.escape(cmd)) + else: + output += self.read_until_prompt() if self.check_config_mode(): raise ValueError("Failed to exit configuration mode") return output @@ -765,6 +1088,13 @@

    Index

  • Classes

    • +

      NokiaSrosFileTransfer

      + +
    • +
    • NokiaSrosSSH

      • check_config_mode
      • diff --git a/docs/netmiko/scp_functions.html b/docs/netmiko/scp_functions.html index 58f45a69f..b85b6d7bf 100644 --- a/docs/netmiko/scp_functions.html +++ b/docs/netmiko/scp_functions.html @@ -55,6 +55,7 @@

        Module netmiko.scp_functions

        inline_transfer=False, overwrite_file=False, socket_timeout=10.0, + verify_file=None, ): """Use Secure Copy or Inline (IOS-only) to transfer files to/from network devices. @@ -89,6 +90,10 @@

        Module netmiko.scp_functions

        if not cisco_ios and inline_transfer: raise ValueError("Inline Transfer only supported for Cisco IOS/Cisco IOS-XE") + # Replace disable_md5 argument with verify_file argument across time + if verify_file is None: + verify_file = not disable_md5 + scp_args = { "ssh_conn": ssh_conn, "source_file": source_file, @@ -104,13 +109,13 @@

        Module netmiko.scp_functions

        with TransferClass(**scp_args) as scp_transfer: if scp_transfer.check_file_exists(): if overwrite_file: - if not disable_md5: - if scp_transfer.compare_md5(): + if verify_file: + if scp_transfer.verify_file(): return nottransferred_but_verified else: # File exists, you can overwrite it, MD5 is wrong (transfer file) verifyspace_and_transferfile(scp_transfer) - if scp_transfer.compare_md5(): + if scp_transfer.verify_file(): return transferred_and_verified else: raise ValueError( @@ -122,16 +127,16 @@

        Module netmiko.scp_functions

        return transferred_and_notverified else: # File exists, but you can't overwrite it. - if not disable_md5: - if scp_transfer.compare_md5(): + if verify_file: + if scp_transfer.verify_file(): return nottransferred_but_verified msg = "File already exists and overwrite_file is disabled" raise ValueError(msg) else: verifyspace_and_transferfile(scp_transfer) # File doesn't exist - if not disable_md5: - if scp_transfer.compare_md5(): + if verify_file: + if scp_transfer.verify_file(): return transferred_and_verified else: raise ValueError("MD5 failure between source and destination files") @@ -147,7 +152,7 @@

        Module netmiko.scp_functions

        Functions

        -def file_transfer(ssh_conn, source_file, dest_file, file_system=None, direction='put', disable_md5=False, inline_transfer=False, overwrite_file=False, socket_timeout=10.0) +def file_transfer(ssh_conn, source_file, dest_file, file_system=None, direction='put', disable_md5=False, inline_transfer=False, overwrite_file=False, socket_timeout=10.0, verify_file=None)

        Use Secure Copy or Inline (IOS-only) to transfer files to/from network devices.

        @@ -169,6 +174,7 @@

        Functions

        inline_transfer=False, overwrite_file=False, socket_timeout=10.0, + verify_file=None, ): """Use Secure Copy or Inline (IOS-only) to transfer files to/from network devices. @@ -203,6 +209,10 @@

        Functions

        if not cisco_ios and inline_transfer: raise ValueError("Inline Transfer only supported for Cisco IOS/Cisco IOS-XE") + # Replace disable_md5 argument with verify_file argument across time + if verify_file is None: + verify_file = not disable_md5 + scp_args = { "ssh_conn": ssh_conn, "source_file": source_file, @@ -218,13 +228,13 @@

        Functions

        with TransferClass(**scp_args) as scp_transfer: if scp_transfer.check_file_exists(): if overwrite_file: - if not disable_md5: - if scp_transfer.compare_md5(): + if verify_file: + if scp_transfer.verify_file(): return nottransferred_but_verified else: # File exists, you can overwrite it, MD5 is wrong (transfer file) verifyspace_and_transferfile(scp_transfer) - if scp_transfer.compare_md5(): + if scp_transfer.verify_file(): return transferred_and_verified else: raise ValueError( @@ -236,16 +246,16 @@

        Functions

        return transferred_and_notverified else: # File exists, but you can't overwrite it. - if not disable_md5: - if scp_transfer.compare_md5(): + if verify_file: + if scp_transfer.verify_file(): return nottransferred_but_verified msg = "File already exists and overwrite_file is disabled" raise ValueError(msg) else: verifyspace_and_transferfile(scp_transfer) # File doesn't exist - if not disable_md5: - if scp_transfer.compare_md5(): + if verify_file: + if scp_transfer.verify_file(): return transferred_and_verified else: raise ValueError("MD5 failure between source and destination files") diff --git a/docs/netmiko/scp_handler.html b/docs/netmiko/scp_handler.html index 20604c85b..bb2d1d044 100644 --- a/docs/netmiko/scp_handler.html +++ b/docs/netmiko/scp_handler.html @@ -92,6 +92,7 @@

        Module netmiko.scp_handler

        file_system=None, direction="put", socket_timeout=10.0, + hash_supported=True, ): self.ssh_ctl_chan = ssh_conn self.source_file = source_file @@ -113,10 +114,12 @@

        Module netmiko.scp_handler

        self.file_system = file_system if direction == "put": - self.source_md5 = self.file_md5(source_file) + self.source_md5 = self.file_md5(source_file) if hash_supported else None self.file_size = os.stat(source_file).st_size elif direction == "get": - self.source_md5 = self.remote_md5(remote_file=source_file) + self.source_md5 = ( + self.remote_md5(remote_file=source_file) if hash_supported else None + ) self.file_size = self.remote_file_size(remote_file=source_file) else: raise ValueError("Invalid direction specified") @@ -418,7 +421,7 @@

        Classes

        class BaseFileTransfer -(ssh_conn, source_file, dest_file, file_system=None, direction='put', socket_timeout=10.0) +(ssh_conn, source_file, dest_file, file_system=None, direction='put', socket_timeout=10.0, hash_supported=True)

        Class to manage SCP file transfer and associated SSH control channel.

        @@ -435,6 +438,7 @@

        Classes

        file_system=None, direction="put", socket_timeout=10.0, + hash_supported=True, ): self.ssh_ctl_chan = ssh_conn self.source_file = source_file @@ -456,10 +460,12 @@

        Classes

        self.file_system = file_system if direction == "put": - self.source_md5 = self.file_md5(source_file) + self.source_md5 = self.file_md5(source_file) if hash_supported else None self.file_size = os.stat(source_file).st_size elif direction == "get": - self.source_md5 = self.remote_md5(remote_file=source_file) + self.source_md5 = ( + self.remote_md5(remote_file=source_file) if hash_supported else None + ) self.file_size = self.remote_file_size(remote_file=source_file) else: raise ValueError("Invalid direction specified") @@ -755,6 +761,7 @@

        Subclasses

      • CienaSaosFileTransfer
      • DellOS10FileTransfer
      • JuniperFileTransfer
      • +
      • NokiaSrosFileTransfer

      Static methods

      diff --git a/docs/netmiko/ssh_autodetect.html b/docs/netmiko/ssh_autodetect.html index 32db55db6..593dbfc73 100644 --- a/docs/netmiko/ssh_autodetect.html +++ b/docs/netmiko/ssh_autodetect.html @@ -166,12 +166,27 @@

      Netmiko connection creation section "priority": 99, "dispatch": "_autodetect_std", }, + "dell_os9": { + "cmd": "show system", + "search_patterns": [ + r"Dell Application Software Version: 9", + r"Dell Networking OS Version : 9", + ], + "priority": 99, + "dispatch": "_autodetect_std", + }, "dell_os10": { "cmd": "show version", "search_patterns": [r"Dell EMC Networking OS10-Enterprise"], "priority": 99, "dispatch": "_autodetect_std", }, + "dell_powerconnect": { + "cmd": "show system", + "search_patterns": [r"PowerConnect"], + "priority": 99, + "dispatch": "_autodetect_std", + }, "f5_tmsh": { "cmd": "show sys version", "search_patterns": [r"BIG-IP"], @@ -228,6 +243,17 @@

      Netmiko connection creation section "priority": 99, "dispatch": "_autodetect_std", }, + "cisco_wlc": { + "dispatch": "_autodetect_remote_version", + "search_patterns": [r"CISCO_WLC"], + "priority": 99, + }, + "mellanox_mlnxos": { + "cmd": "show version", + "search_patterns": [r"Onyx", r"SX_PPC_M460EX"], + "priority": 99, + "dispatch": "_autodetect_std", + }, } @@ -349,7 +375,45 @@

      Netmiko connection creation section else: return cached_results - def _autodetect_std(self, cmd="", search_patterns=None, re_flags=re.I, priority=99): + def _autodetect_remote_version( + self, search_patterns=None, re_flags=re.IGNORECASE, priority=99 + ): + """ + Method to try auto-detect the device type, by matching a regular expression on the reported + remote version of the SSH server. + + Parameters + ---------- + search_patterns : list + A list of regular expression to look for in the reported remote SSH version + (default: None). + re_flags: re.flags, optional + Any flags from the python re module to modify the regular expression (default: re.I). + priority: int, optional + The confidence the match is right between 0 and 99 (default: 99). + """ + invalid_responses = [r"^$"] + + if not search_patterns: + return 0 + + try: + remote_version = self.connection.remote_conn.transport.remote_version + for pattern in invalid_responses: + match = re.search(pattern, remote_version, flags=re.I) + if match: + return 0 + for pattern in search_patterns: + match = re.search(pattern, remote_version, flags=re_flags) + if match: + return priority + except Exception: + return 0 + return 0 + + def _autodetect_std( + self, cmd="", search_patterns=None, re_flags=re.IGNORECASE, priority=99 + ): """ Standard method to try to auto-detect the device type. This method will be called for each device_type present in SSH_MAPPER_BASE dict ('dispatch' key). It will attempt to send a @@ -550,7 +614,45 @@

      Methods

      else: return cached_results - def _autodetect_std(self, cmd="", search_patterns=None, re_flags=re.I, priority=99): + def _autodetect_remote_version( + self, search_patterns=None, re_flags=re.IGNORECASE, priority=99 + ): + """ + Method to try auto-detect the device type, by matching a regular expression on the reported + remote version of the SSH server. + + Parameters + ---------- + search_patterns : list + A list of regular expression to look for in the reported remote SSH version + (default: None). + re_flags: re.flags, optional + Any flags from the python re module to modify the regular expression (default: re.I). + priority: int, optional + The confidence the match is right between 0 and 99 (default: 99). + """ + invalid_responses = [r"^$"] + + if not search_patterns: + return 0 + + try: + remote_version = self.connection.remote_conn.transport.remote_version + for pattern in invalid_responses: + match = re.search(pattern, remote_version, flags=re.I) + if match: + return 0 + for pattern in search_patterns: + match = re.search(pattern, remote_version, flags=re_flags) + if match: + return priority + except Exception: + return 0 + return 0 + + def _autodetect_std( + self, cmd="", search_patterns=None, re_flags=re.IGNORECASE, priority=99 + ): """ Standard method to try to auto-detect the device type. This method will be called for each device_type present in SSH_MAPPER_BASE dict ('dispatch' key). It will attempt to send a diff --git a/docs/netmiko/ubiquiti/edge_ssh.html b/docs/netmiko/ubiquiti/edge_ssh.html index a36cd4019..66c4ae32d 100644 --- a/docs/netmiko/ubiquiti/edge_ssh.html +++ b/docs/netmiko/ubiquiti/edge_ssh.html @@ -264,6 +264,10 @@

      Ancestors

    • CiscoBaseConnection
    • BaseConnection
    +

    Subclasses

    +

    Methods

    diff --git a/docs/netmiko/ubiquiti/index.html b/docs/netmiko/ubiquiti/index.html index fe2222131..c992b98c8 100644 --- a/docs/netmiko/ubiquiti/index.html +++ b/docs/netmiko/ubiquiti/index.html @@ -23,8 +23,9 @@

    Module netmiko.ubiquiti

    Source code
    from netmiko.ubiquiti.edge_ssh import UbiquitiEdgeSSH
    +from netmiko.ubiquiti.unifiswitch_ssh import UbiquitiUnifiSwitchSSH
     
    -__all__ = ["UbiquitiEdgeSSH"]
    +__all__ = ["UbiquitiEdgeSSH", "UnifiSwitchSSH", "UbiquitiUnifiSwitchSSH"]
  • @@ -34,6 +35,10 @@

    Sub-modules

    +
    netmiko.ubiquiti.unifiswitch_ssh
    +
    +
    +
    @@ -228,6 +233,10 @@

    Ancestors

  • CiscoBaseConnection
  • BaseConnection
  • +

    Subclasses

    +

    Methods

    @@ -336,6 +345,270 @@

    Inherited members

    +
    +class UbiquitiUnifiSwitchSSH +(ip='', host='', username='', password=None, secret='', port=None, device_type='', verbose=False, global_delay_factor=1, global_cmd_verify=None, use_keys=False, key_file=None, pkey=None, passphrase=None, allow_agent=False, ssh_strict=False, system_host_keys=False, alt_host_keys=False, alt_key_file='', ssh_config_file=None, timeout=100, session_timeout=60, auth_timeout=None, blocking_timeout=20, banner_timeout=15, keepalive=0, default_enter=None, response_return=None, serial_settings=None, fast_cli=False, session_log=None, session_log_record_writes=False, session_log_file_mode='write', allow_auto_change=False, encoding='ascii', sock=None) +
    +
    +

    Implements support for Ubiquity EdgeSwitch devices.

    +

    Mostly conforms to Cisco IOS style syntax with a few minor changes.

    +

    This is NOT for EdgeRouter devices.

    +
        Initialize attributes for establishing connection to target device.
    +
    +    :param ip: IP address of target device. Not required if `host` is
    +        provided.
    +    :type ip: str
    +
    +    :param host: Hostname of target device. Not required if `ip` is
    +            provided.
    +    :type host: str
    +
    +    :param username: Username to authenticate against target device if
    +            required.
    +    :type username: str
    +
    +    :param password: Password to authenticate against target device if
    +            required.
    +    :type password: str
    +
    +    :param secret: The enable password if target device requires one.
    +    :type secret: str
    +
    +    :param port: The destination port used to connect to the target
    +            device.
    +    :type port: int or None
    +
    +    :param device_type: Class selection based on device type.
    +    :type device_type: str
    +
    +    :param verbose: Enable additional messages to standard output.
    +    :type verbose: bool
    +
    +    :param global_delay_factor: Multiplication factor affecting Netmiko delays (default: 1).
    +    :type global_delay_factor: int
    +
    +    :param use_keys: Connect to target device using SSH keys.
    +    :type use_keys: bool
    +
    +    :param key_file: Filename path of the SSH key file to use.
    +    :type key_file: str
    +
    +    :param pkey: SSH key object to use.
    +    :type pkey: paramiko.PKey
    +
    +    :param passphrase: Passphrase to use for encrypted key; password will be used for key
    +            decryption if not specified.
    +    :type passphrase: str
    +
    +    :param allow_agent: Enable use of SSH key-agent.
    +    :type allow_agent: bool
    +
    +    :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: Load host keys from the users known_hosts file.
    +    :type system_host_keys: bool
    +    :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).
    +    :type alt_key_file: str
    +
    +    :param ssh_config_file: File name of OpenSSH configuration file.
    +    :type ssh_config_file: str
    +
    +    :param timeout: Connection timeout.
    +    :type timeout: float
    +
    +    :param session_timeout: Set a timeout for parallel requests.
    +    :type session_timeout: float
    +
    +    :param auth_timeout: Set a timeout (in seconds) to wait for an authentication response.
    +    :type auth_timeout: float
    +
    +    :param banner_timeout: Set a timeout to wait for the SSH banner (pass to Paramiko).
    +    :type banner_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
    +
    +    :param default_enter: Character(s) to send to correspond to enter key (default:
    +
    +

    ). +:type default_enter: str

    +
        :param response_return: Character(s) to use in normalized return data to represent
    +            enter key (default:
    +
    +

    ) +:type response_return: str

    +
        :param fast_cli: Provide a way to optimize for performance. Converts select_delay_factor
    +            to select smallest of global and specific. Sets default global_delay_factor to .1
    +            (default: False)
    +    :type fast_cli: boolean
    +
    +    :param session_log: File path or BufferedIOBase subclass object to write the session log to.
    +    :type session_log: str
    +
    +    :param session_log_record_writes: The session log generally only records channel reads due
    +            to eliminate command duplication due to command echo. You can enable this if you
    +            want to record both channel reads and channel writes in the log (default: False).
    +    :type session_log_record_writes: boolean
    +
    +    :param session_log_file_mode: "write" or "append" for session_log file mode
    +            (default: "write")
    +    :type session_log_file_mode: str
    +
    +    :param allow_auto_change: Allow automatic configuration changes for terminal settings.
    +            (default: False)
    +    :type allow_auto_change: bool
    +
    +    :param encoding: Encoding to be used when writing bytes to the output channel.
    +            (default: ascii)
    +    :type encoding: str
    +
    +    :param sock: An open socket or socket-like object (such as a `.Channel`) to use for
    +            communication to the target host (default: None).
    +    :type sock: socket
    +
    +    :param global_cmd_verify: Control whether command echo verification is enabled or disabled
    +            (default: None). Global attribute takes precedence over function `cmd_verify`
    +            argument. Value of `None` indicates to use function `cmd_verify` argument.
    +    :type global_cmd_verify: bool|None
    +
    +
    +Source code +
    class UbiquitiUnifiSwitchSSH(UbiquitiEdgeSSH):
    +    def session_preparation(self):
    +        """
    +        Prepare the session after the connection has been established.
    +        When SSHing to a UniFi switch, the session initially starts at a Linux
    +        shell. Nothing interesting can be done in this environment, however,
    +        running `telnet localhost` drops the session to a more familiar
    +        environment.
    +        """
    +
    +        self._test_channel_read()
    +        self.set_base_prompt()
    +        self.send_command(
    +            command_string="telnet localhost", expect_string=r"\(UBNT\) >"
    +        )
    +        self.set_base_prompt()
    +        self.enable()
    +        self.disable_paging()
    +
    +        # Clear read buffer
    +        time.sleep(0.3 * self.global_delay_factor)
    +        self.clear_buffer()
    +
    +    def cleanup(self, command="exit"):
    +        """Gracefully exit the SSH session."""
    +        try:
    +            # The pattern="" forces use of send_command_timing
    +            if self.check_config_mode(pattern=""):
    +                self.exit_config_mode()
    +
    +            # Exit from the first 'telnet localhost'
    +            self.write_channel(command + self.RETURN)
    +        except Exception:
    +            pass
    +
    +        super().cleanup()
    +
    +

    Ancestors

    + +

    Methods

    +
    +
    +def session_preparation(self) +
    +
    +

    Prepare the session after the connection has been established. +When SSHing to a UniFi switch, the session initially starts at a Linux +shell. Nothing interesting can be done in this environment, however, +running telnet localhost drops the session to a more familiar +environment.

    +
    +Source code +
    def session_preparation(self):
    +    """
    +    Prepare the session after the connection has been established.
    +    When SSHing to a UniFi switch, the session initially starts at a Linux
    +    shell. Nothing interesting can be done in this environment, however,
    +    running `telnet localhost` drops the session to a more familiar
    +    environment.
    +    """
    +
    +    self._test_channel_read()
    +    self.set_base_prompt()
    +    self.send_command(
    +        command_string="telnet localhost", expect_string=r"\(UBNT\) >"
    +    )
    +    self.set_base_prompt()
    +    self.enable()
    +    self.disable_paging()
    +
    +    # Clear read buffer
    +    time.sleep(0.3 * self.global_delay_factor)
    +    self.clear_buffer()
    +
    +
    +
    +

    Inherited members

    + +
    @@ -353,6 +626,7 @@

    Index

  • Sub-modules

  • Classes

    @@ -367,6 +641,12 @@

    save_config

  • +
  • +

    UbiquitiUnifiSwitchSSH

    + +
  • diff --git a/docs/netmiko/ubiquiti/unifiswitch_ssh.html b/docs/netmiko/ubiquiti/unifiswitch_ssh.html new file mode 100644 index 000000000..46d4db64f --- /dev/null +++ b/docs/netmiko/ubiquiti/unifiswitch_ssh.html @@ -0,0 +1,373 @@ + + + + + + +netmiko.ubiquiti.unifiswitch_ssh API documentation + + + + + + + + + +
    +
    +
    +

    Module netmiko.ubiquiti.unifiswitch_ssh

    +
    +
    +
    +Source code +
    import time
    +from netmiko.ubiquiti.edge_ssh import UbiquitiEdgeSSH
    +
    +
    +class UbiquitiUnifiSwitchSSH(UbiquitiEdgeSSH):
    +    def session_preparation(self):
    +        """
    +        Prepare the session after the connection has been established.
    +        When SSHing to a UniFi switch, the session initially starts at a Linux
    +        shell. Nothing interesting can be done in this environment, however,
    +        running `telnet localhost` drops the session to a more familiar
    +        environment.
    +        """
    +
    +        self._test_channel_read()
    +        self.set_base_prompt()
    +        self.send_command(
    +            command_string="telnet localhost", expect_string=r"\(UBNT\) >"
    +        )
    +        self.set_base_prompt()
    +        self.enable()
    +        self.disable_paging()
    +
    +        # Clear read buffer
    +        time.sleep(0.3 * self.global_delay_factor)
    +        self.clear_buffer()
    +
    +    def cleanup(self, command="exit"):
    +        """Gracefully exit the SSH session."""
    +        try:
    +            # The pattern="" forces use of send_command_timing
    +            if self.check_config_mode(pattern=""):
    +                self.exit_config_mode()
    +
    +            # Exit from the first 'telnet localhost'
    +            self.write_channel(command + self.RETURN)
    +        except Exception:
    +            pass
    +
    +        super().cleanup()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Classes

    +
    +
    +class UbiquitiUnifiSwitchSSH +(ip='', host='', username='', password=None, secret='', port=None, device_type='', verbose=False, global_delay_factor=1, global_cmd_verify=None, use_keys=False, key_file=None, pkey=None, passphrase=None, allow_agent=False, ssh_strict=False, system_host_keys=False, alt_host_keys=False, alt_key_file='', ssh_config_file=None, timeout=100, session_timeout=60, auth_timeout=None, blocking_timeout=20, banner_timeout=15, keepalive=0, default_enter=None, response_return=None, serial_settings=None, fast_cli=False, session_log=None, session_log_record_writes=False, session_log_file_mode='write', allow_auto_change=False, encoding='ascii', sock=None) +
    +
    +

    Implements support for Ubiquity EdgeSwitch devices.

    +

    Mostly conforms to Cisco IOS style syntax with a few minor changes.

    +

    This is NOT for EdgeRouter devices.

    +
        Initialize attributes for establishing connection to target device.
    +
    +    :param ip: IP address of target device. Not required if `host` is
    +        provided.
    +    :type ip: str
    +
    +    :param host: Hostname of target device. Not required if `ip` is
    +            provided.
    +    :type host: str
    +
    +    :param username: Username to authenticate against target device if
    +            required.
    +    :type username: str
    +
    +    :param password: Password to authenticate against target device if
    +            required.
    +    :type password: str
    +
    +    :param secret: The enable password if target device requires one.
    +    :type secret: str
    +
    +    :param port: The destination port used to connect to the target
    +            device.
    +    :type port: int or None
    +
    +    :param device_type: Class selection based on device type.
    +    :type device_type: str
    +
    +    :param verbose: Enable additional messages to standard output.
    +    :type verbose: bool
    +
    +    :param global_delay_factor: Multiplication factor affecting Netmiko delays (default: 1).
    +    :type global_delay_factor: int
    +
    +    :param use_keys: Connect to target device using SSH keys.
    +    :type use_keys: bool
    +
    +    :param key_file: Filename path of the SSH key file to use.
    +    :type key_file: str
    +
    +    :param pkey: SSH key object to use.
    +    :type pkey: paramiko.PKey
    +
    +    :param passphrase: Passphrase to use for encrypted key; password will be used for key
    +            decryption if not specified.
    +    :type passphrase: str
    +
    +    :param allow_agent: Enable use of SSH key-agent.
    +    :type allow_agent: bool
    +
    +    :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: Load host keys from the users known_hosts file.
    +    :type system_host_keys: bool
    +    :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).
    +    :type alt_key_file: str
    +
    +    :param ssh_config_file: File name of OpenSSH configuration file.
    +    :type ssh_config_file: str
    +
    +    :param timeout: Connection timeout.
    +    :type timeout: float
    +
    +    :param session_timeout: Set a timeout for parallel requests.
    +    :type session_timeout: float
    +
    +    :param auth_timeout: Set a timeout (in seconds) to wait for an authentication response.
    +    :type auth_timeout: float
    +
    +    :param banner_timeout: Set a timeout to wait for the SSH banner (pass to Paramiko).
    +    :type banner_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
    +
    +    :param default_enter: Character(s) to send to correspond to enter key (default:
    +
    +

    ). +:type default_enter: str

    +
        :param response_return: Character(s) to use in normalized return data to represent
    +            enter key (default:
    +
    +

    ) +:type response_return: str

    +
        :param fast_cli: Provide a way to optimize for performance. Converts select_delay_factor
    +            to select smallest of global and specific. Sets default global_delay_factor to .1
    +            (default: False)
    +    :type fast_cli: boolean
    +
    +    :param session_log: File path or BufferedIOBase subclass object to write the session log to.
    +    :type session_log: str
    +
    +    :param session_log_record_writes: The session log generally only records channel reads due
    +            to eliminate command duplication due to command echo. You can enable this if you
    +            want to record both channel reads and channel writes in the log (default: False).
    +    :type session_log_record_writes: boolean
    +
    +    :param session_log_file_mode: "write" or "append" for session_log file mode
    +            (default: "write")
    +    :type session_log_file_mode: str
    +
    +    :param allow_auto_change: Allow automatic configuration changes for terminal settings.
    +            (default: False)
    +    :type allow_auto_change: bool
    +
    +    :param encoding: Encoding to be used when writing bytes to the output channel.
    +            (default: ascii)
    +    :type encoding: str
    +
    +    :param sock: An open socket or socket-like object (such as a `.Channel`) to use for
    +            communication to the target host (default: None).
    +    :type sock: socket
    +
    +    :param global_cmd_verify: Control whether command echo verification is enabled or disabled
    +            (default: None). Global attribute takes precedence over function `cmd_verify`
    +            argument. Value of `None` indicates to use function `cmd_verify` argument.
    +    :type global_cmd_verify: bool|None
    +
    +
    +Source code +
    class UbiquitiUnifiSwitchSSH(UbiquitiEdgeSSH):
    +    def session_preparation(self):
    +        """
    +        Prepare the session after the connection has been established.
    +        When SSHing to a UniFi switch, the session initially starts at a Linux
    +        shell. Nothing interesting can be done in this environment, however,
    +        running `telnet localhost` drops the session to a more familiar
    +        environment.
    +        """
    +
    +        self._test_channel_read()
    +        self.set_base_prompt()
    +        self.send_command(
    +            command_string="telnet localhost", expect_string=r"\(UBNT\) >"
    +        )
    +        self.set_base_prompt()
    +        self.enable()
    +        self.disable_paging()
    +
    +        # Clear read buffer
    +        time.sleep(0.3 * self.global_delay_factor)
    +        self.clear_buffer()
    +
    +    def cleanup(self, command="exit"):
    +        """Gracefully exit the SSH session."""
    +        try:
    +            # The pattern="" forces use of send_command_timing
    +            if self.check_config_mode(pattern=""):
    +                self.exit_config_mode()
    +
    +            # Exit from the first 'telnet localhost'
    +            self.write_channel(command + self.RETURN)
    +        except Exception:
    +            pass
    +
    +        super().cleanup()
    +
    +

    Ancestors

    + +

    Methods

    +
    +
    +def session_preparation(self) +
    +
    +

    Prepare the session after the connection has been established. +When SSHing to a UniFi switch, the session initially starts at a Linux +shell. Nothing interesting can be done in this environment, however, +running telnet localhost drops the session to a more familiar +environment.

    +
    +Source code +
    def session_preparation(self):
    +    """
    +    Prepare the session after the connection has been established.
    +    When SSHing to a UniFi switch, the session initially starts at a Linux
    +    shell. Nothing interesting can be done in this environment, however,
    +    running `telnet localhost` drops the session to a more familiar
    +    environment.
    +    """
    +
    +    self._test_channel_read()
    +    self.set_base_prompt()
    +    self.send_command(
    +        command_string="telnet localhost", expect_string=r"\(UBNT\) >"
    +    )
    +    self.set_base_prompt()
    +    self.enable()
    +    self.disable_paging()
    +
    +    # Clear read buffer
    +    time.sleep(0.3 * self.global_delay_factor)
    +    self.clear_buffer()
    +
    +
    +
    +

    Inherited members

    + +
    +
    +
    +
    + +
    + + + + + \ No newline at end of file diff --git a/docs/netmiko/vyos/index.html b/docs/netmiko/vyos/index.html index d4a6393af..21a6cf9f6 100644 --- a/docs/netmiko/vyos/index.html +++ b/docs/netmiko/vyos/index.html @@ -185,6 +185,7 @@

    Classes

    self._test_channel_read() self.set_base_prompt() self.disable_paging(command="set terminal length 0") + self.set_terminal_width(command="set terminal width 512") # Clear the read buffer time.sleep(0.3 * self.global_delay_factor) self.clear_buffer() @@ -454,6 +455,7 @@

    Methods

    self._test_channel_read() self.set_base_prompt() self.disable_paging(command="set terminal length 0") + self.set_terminal_width(command="set terminal width 512") # Clear the read buffer time.sleep(0.3 * self.global_delay_factor) self.clear_buffer()
    diff --git a/docs/netmiko/vyos/vyos_ssh.html b/docs/netmiko/vyos/vyos_ssh.html index 56a6d45b9..b28129b2c 100644 --- a/docs/netmiko/vyos/vyos_ssh.html +++ b/docs/netmiko/vyos/vyos_ssh.html @@ -34,6 +34,7 @@

    Module netmiko.vyos.vyos_ssh

    self._test_channel_read() self.set_base_prompt() self.disable_paging(command="set terminal length 0") + self.set_terminal_width(command="set terminal width 512") # Clear the read buffer time.sleep(0.3 * self.global_delay_factor) self.clear_buffer() @@ -280,6 +281,7 @@

    Classes

    self._test_channel_read() self.set_base_prompt() self.disable_paging(command="set terminal length 0") + self.set_terminal_width(command="set terminal width 512") # Clear the read buffer time.sleep(0.3 * self.global_delay_factor) self.clear_buffer() @@ -549,6 +551,7 @@

    Methods

    self._test_channel_read() self.set_base_prompt() self.disable_paging(command="set terminal length 0") + self.set_terminal_width(command="set terminal width 512") # Clear the read buffer time.sleep(0.3 * self.global_delay_factor) self.clear_buffer() diff --git a/netmiko/__init__.py b/netmiko/__init__.py index 7180d1bdb..66b3d6cb1 100644 --- a/netmiko/__init__.py +++ b/netmiko/__init__.py @@ -23,7 +23,7 @@ # Alternate naming Netmiko = ConnectHandler -__version__ = "3.1.0" +__version__ = "3.1.1" __all__ = ( "ConnectHandler", "ssh_dispatcher", diff --git a/tests/test_suite_alt.sh b/tests/test_suite_alt.sh index ba7e03f82..865a549d5 100755 --- a/tests/test_suite_alt.sh +++ b/tests/test_suite_alt.sh @@ -71,6 +71,11 @@ echo "Starting tests...good luck:" \ && py.test -v test_netmiko_config.py --test_device cisco_xrv \ && py.test -v test_netmiko_commit.py --test_device cisco_xrv \ \ +&& echo "Cisco IOS-XR (Azure)" \ +&& py.test -v test_netmiko_show.py --test_device cisco_xr_azure \ +&& py.test -v test_netmiko_config.py --test_device cisco_xr_azure \ +&& py.test -v test_netmiko_commit.py --test_device cisco_xr_azure \ +\ && echo "Cisco NXOS" \ && py.test -v test_netmiko_scp.py --test_device nxos1 \ && py.test -v test_netmiko_show.py --test_device nxos1 \ @@ -86,6 +91,7 @@ echo "Starting tests...good luck:" \ && py.test -s -v test_netmiko_autodetect.py --test_device juniper_srx \ && py.test -s -v test_netmiko_autodetect.py --test_device cisco_asa \ && py.test -s -v test_netmiko_autodetect.py --test_device cisco_xrv \ +&& py.test -s -v test_netmiko_autodetect.py --test_device cisco_xr_azure \ \ && echo "HP ProCurve" \ && py.test -v test_netmiko_show.py --test_device hp_procurve \