diff --git a/examples/DEVICE_CREDS.py b/examples/DEVICE_CREDS.py deleted file mode 100644 index edf29e4fe..000000000 --- a/examples/DEVICE_CREDS.py +++ /dev/null @@ -1,98 +0,0 @@ -cisco_881 = { - 'device_type': 'cisco_ios', - 'ip': '10.10.10.227', - 'username': 'test1', - 'password': 'password', - 'secret': 'secret', - 'verbose': False, -} - -cisco_asa = { - 'device_type': 'cisco_asa', - 'ip': '10.10.10.226', - 'username': 'admin', - 'password': 'password', - 'secret': 'secret', - 'verbose': False, -} - -arista_veos_sw1 = { - 'device_type': 'arista_eos', - 'ip': '10.10.10.227', - 'username': 'admin1', - 'password': 'password', - 'secret': '', - 'port': 8222, - 'verbose': False, -} - -arista_veos_sw2 = { - 'device_type': 'arista_eos', - 'ip': '10.10.10.227', - 'username': 'admin1', - 'password': 'password', - 'secret': '', - 'port': 8322, - 'verbose': False, -} - -arista_veos_sw3 = { - 'device_type': 'arista_eos', - 'ip': '10.10.10.227', - 'username': 'admin1', - 'password': 'password', - 'secret': '', - 'port': 8422, - 'verbose': False, -} - -arista_veos_sw4 = { - 'device_type': 'arista_eos', - 'ip': '10.10.10.227', - 'username': 'admin1', - 'password': 'password', - 'secret': '', - 'port': 8522, - 'verbose': False, -} - -hp_procurve = { - 'device_type': 'hp_procurve', - 'ip': '10.10.10.227', - 'username': 'admin', - 'password': 'password', - 'secret': '', - 'port': 9922, - 'verbose': False, -} - -hp_comware = { - 'device_type': 'hp_comware', - 'ip': '192.168.112.11', - 'username': 'admin', - 'password': 'admin', - 'port': 22, - 'verbose': False, -} - -brocade_vdx = { - 'device_type': 'brocade_vdx', - 'ip': '10.254.8.8', - 'username': 'admin', - 'password': 'password', - 'port': 22, - 'verbose': False, -} - - -all_devices = [ - cisco_881, - cisco_asa, - arista_veos_sw1, - arista_veos_sw2, - arista_veos_sw3, - arista_veos_sw4, - hp_procurve, - hp_comware, - brocade_vdx, -] diff --git a/examples/adding_delay/add_delay.py b/examples/adding_delay/add_delay.py new file mode 100755 index 000000000..75f6d275c --- /dev/null +++ b/examples/adding_delay/add_delay.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +from __future__ import print_function, unicode_literals + +# Netmiko is the same as ConnectHandler +from netmiko import Netmiko +from getpass import getpass + +my_device = { + 'host': "host.domain.com", + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios', + # Increase (essentially) all sleeps by a factor of 2 + 'global_delay_factor': 2, +} + +net_connect = Netmiko(**my_device) +# Increase the sleeps for just send_command by a factor of 2 +output = net_connect.send_command("show ip int brief", delay_factor=2) +print(output) +net_connect.disconnect() diff --git a/examples/asa_upgrade.py b/examples/asa_upgrade.py index 5406902cb..62c48853d 100755 --- a/examples/asa_upgrade.py +++ b/examples/asa_upgrade.py @@ -1,22 +1,27 @@ #!/usr/bin/env python """Script to upgrade a Cisco ASA.""" -import sys +from __future__ import print_function from datetime import datetime from getpass import getpass from netmiko import ConnectHandler, FileTransfer + def asa_scp_handler(ssh_conn, cmd='ssh scopy enable', mode='enable'): """Enable/disable SCP on Cisco ASA.""" if mode == 'disable': cmd = 'no ' + cmd return ssh_conn.send_config_set([cmd]) + def main(): """Script to upgrade a Cisco ASA.""" - ip_addr = raw_input("Enter ASA IP address: ") + try: + ip_addr = raw_input("Enter ASA IP address: ") + except NameError: + ip_addr = input("Enter ASA IP address: ") my_pass = getpass() start_time = datetime.now() - print ">>>> {}".format(start_time) + print(">>>> {}".format(start_time)) net_device = { 'device_type': 'cisco_asa', @@ -27,16 +32,15 @@ def main(): 'port': 22, } - print "\nLogging in to ASA" + print("\nLogging in to ASA") ssh_conn = ConnectHandler(**net_device) - print + print() # ADJUST TO TRANSFER IMAGE FILE dest_file_system = 'disk0:' source_file = 'test1.txt' dest_file = 'test1.txt' alt_dest_file = 'asa825-59-k8.bin' - scp_changed = False with FileTransfer(ssh_conn, source_file=source_file, dest_file=dest_file, file_system=dest_file_system) as scp_transfer: @@ -45,42 +49,43 @@ def main(): if not scp_transfer.verify_space_available(): raise ValueError("Insufficient space available on remote device") - print "Enabling SCP" + print("Enabling SCP") output = asa_scp_handler(ssh_conn, mode='enable') - print output + print(output) - print "\nTransferring file\n" + print("\nTransferring file\n") scp_transfer.transfer_file() - print "Disabling SCP" + print("Disabling SCP") output = asa_scp_handler(ssh_conn, mode='disable') - print output + print(output) - print "\nVerifying file" + print("\nVerifying file") if scp_transfer.verify_file(): - print "Source and destination MD5 matches" + print("Source and destination MD5 matches") else: raise ValueError("MD5 failure between source and destination files") - print "\nSending boot commands" + print("\nSending boot commands") full_file_name = "{}/{}".format(dest_file_system, alt_dest_file) boot_cmd = 'boot system {}'.format(full_file_name) output = ssh_conn.send_config_set([boot_cmd]) - print output + print(output) - print "\nVerifying state" + print("\nVerifying state") output = ssh_conn.send_command('show boot') - print output + print(output) # UNCOMMENT TO PERFORM WR MEM AND RELOAD - #print "\nWrite mem and reload" - #output = ssh_conn.send_command_expect('write mem') - #output += ssh_conn.send_command('reload') - #output += ssh_conn.send_command('y') - #print output - - print "\n>>>> {}".format(datetime.now() - start_time) - print + # print("\nWrite mem and reload") + # output = ssh_conn.send_command_expect('write mem') + # output += ssh_conn.send_command('reload') + # output += ssh_conn.send_command('y') + # print(output) + + print("\n>>>> {}".format(datetime.now() - start_time)) + print() + if __name__ == "__main__": main() diff --git a/examples/ciscoTP_example.py b/examples/ciscoTP_example.py deleted file mode 100644 index 3667e31f0..000000000 --- a/examples/ciscoTP_example.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -''' -Test Netmiko on Cisco TelePresence Video device (C, SX, DX, EX, MX) -''' - -from netmiko import ConnectHandler, cisco - -mydevice = { -'device_type': 'cisco_tp', -'ip': '192.168.105.1', -'username': 'admin', -'password': 'Tandberg', -'verbose':True -} - -ssh_conn = ConnectHandler(**mydevice) -print( "\n\n") - - -command_list = ['help', 'whoami', 'whoaami', 'echo test', 'xconfig'] -#command_list = ['help', 'whoami', 'whoaami', 'echo test'] - -ssh_conn = ConnectHandler(**mydevice) -print( "\n\n") - -for command in command_list: - print('>>> running command : ' + command) - output = ssh_conn.send_command(command) - print('Result = ' + output + '\n') \ No newline at end of file diff --git a/examples/cisco_logging.txt b/examples/cisco_logging.txt deleted file mode 100644 index c237e53a8..000000000 --- a/examples/cisco_logging.txt +++ /dev/null @@ -1,2 +0,0 @@ -logging buffered 8111 -no logging console diff --git a/examples/configuration_changes/change_file.txt b/examples/configuration_changes/change_file.txt new file mode 100644 index 000000000..b331444e6 --- /dev/null +++ b/examples/configuration_changes/change_file.txt @@ -0,0 +1,2 @@ +logging buffered 8000 +logging console diff --git a/examples/configuration_changes/config_changes.py b/examples/configuration_changes/config_changes.py new file mode 100755 index 000000000..fbe18f52a --- /dev/null +++ b/examples/configuration_changes/config_changes.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +from __future__ import print_function, unicode_literals + +# Netmiko is the same as ConnectHandler +from netmiko import Netmiko +from getpass import getpass + +my_device = { + 'host': "host.domain.com", + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios', +} + +net_connect = Netmiko(**my_device) +cfg_commands = ['logging buffered 10000', 'no logging console'] + +# send_config_set() will automatically enter/exit config mode +output = net_connect.send_config_set(cfg_commands) +print(output) + +net_connect.disconnect() diff --git a/examples/configuration_changes/config_changes_from_file.py b/examples/configuration_changes/config_changes_from_file.py new file mode 100755 index 000000000..c5b0eaa88 --- /dev/null +++ b/examples/configuration_changes/config_changes_from_file.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +from __future__ import print_function, unicode_literals + +# Netmiko is the same as ConnectHandler +from netmiko import Netmiko +from getpass import getpass + +my_device = { + 'host': "host.domain.com", + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios', +} + +net_connect = Netmiko(**my_device) + +# Make configuration changes using an external file +output = net_connect.send_config_from_file("change_file.txt") +print(output) + +net_connect.disconnect() diff --git a/examples/connect_multiple_devices/connect_multiple.py b/examples/connect_multiple_devices/connect_multiple.py new file mode 100755 index 000000000..ba67218a7 --- /dev/null +++ b/examples/connect_multiple_devices/connect_multiple.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +""" +This example is serial (i.e. no concurrency). Connect to one device, after the other, +after the other. +""" +from __future__ import print_function, unicode_literals + +# Netmiko is the same as ConnectHandler +from netmiko import Netmiko +from getpass import getpass + +password = getpass() + +cisco1 = { + 'host': "host1.domain.com", + 'username': 'pyclass', + 'password': password, + 'device_type': 'cisco_ios', +} + +arista1 = { + 'host': "host2.domain.com", + 'username': 'pyclass', + 'password': password, + 'device_type': 'arista_eos', +} + +srx1 = { + 'host': "host3.domain.com", + 'username': 'pyclass', + 'password': password, + 'device_type': 'juniper_junos', +} + +for device in (cisco1, arista1, srx1): + net_connect = Netmiko(**device) + print(net_connect.find_prompt()) diff --git a/examples/enable/enable.py b/examples/enable/enable.py new file mode 100755 index 000000000..8c14df8b6 --- /dev/null +++ b/examples/enable/enable.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +from __future__ import print_function, unicode_literals + +# Netmiko is the same as ConnectHandler +from netmiko import Netmiko +from getpass import getpass + +my_device = { + 'host': "host.domain.com", + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios', +} + +net_connect = Netmiko(**my_device) +# Ensure in enable mode +net_connect.enable() +print(net_connect.find_prompt()) + +net_connect.disconnect() diff --git a/examples/handle_prompts/handle_prompts.py b/examples/handle_prompts/handle_prompts.py new file mode 100755 index 000000000..409c95d04 --- /dev/null +++ b/examples/handle_prompts/handle_prompts.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +"""Handling commands that prompt for additional information.""" +from __future__ import print_function, unicode_literals + +# Netmiko is the same as ConnectHandler +from netmiko import Netmiko +from getpass import getpass + +my_device = { + 'host': "host.domain.com", + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios', +} + +""" +Cisco IOS behavior on file delete: + +pynet-rtr1# delete flash:/small_file_bim.txt +Delete flash:/test1.txt? [confirm]y +pynet-rtr1 +""" + +net_connect = Netmiko(**my_device) +filename = "text1234.txt" +cmd = "delete flash:{}".format(filename) + +# send_command_timing as the router prompt is not returned +output = net_connect.send_command_timing(cmd, strip_command=False, strip_prompt=False) +if 'confirm' in output: + output += net_connect.send_command_timing("\n", strip_command=False, strip_prompt=False) + +net_connect.disconnect() +print(output) diff --git a/examples/multiprocess_example.py b/examples/multiprocess_example.py deleted file mode 100644 index 3007806d0..000000000 --- a/examples/multiprocess_example.py +++ /dev/null @@ -1,111 +0,0 @@ -''' -Requires paramiko >=1.8.0 (paramiko had an issue with multiprocessing prior -to this) - -Example code showing how to use netmiko for multiprocessing. Create a -separate process for each ssh connection. Each subprocess executes a -'show version' command on the remote device. Use a multiprocessing.queue to -pass data from subprocess to parent process. - -Only supports Python2 -''' - -# Catch Paramiko warnings about libgmp and RandomPool -import warnings -with warnings.catch_warnings(record=True) as w: - import paramiko - -import multiprocessing -import time -from datetime import datetime - -import netmiko -from netmiko.ssh_exception import NetMikoTimeoutException, NetMikoAuthenticationException - -# DEVICE_CREDS contains the devices to connect to -from DEVICE_CREDS import all_devices - - -def print_output(results): - - print "\nSuccessful devices:" - for a_dict in results: - for identifier,v in a_dict.iteritems(): - (success, out_string) = v - if success: - print '\n\n' - print '#' * 80 - print 'Device = {0}\n'.format(identifier) - print out_string - print '#' * 80 - - print "\n\nFailed devices:\n" - for a_dict in results: - for identifier,v in a_dict.iteritems(): - (success, out_string) = v - if not success: - print 'Device failed = {0}'.format(identifier) - - print "\nEnd time: " + str(datetime.now()) - print - - -def worker_show_version(a_device, mp_queue): - ''' - Return a dictionary where the key is the device identifier - Value is (success|fail(boolean), return_string) - ''' - - try: - a_device['port'] - except KeyError: - a_device['port'] = 22 - - identifier = '{ip}:{port}'.format(**a_device) - return_data = {} - - show_ver_command = 'show version' - SSHClass = netmiko.ssh_dispatcher(a_device['device_type']) - - try: - net_connect = SSHClass(**a_device) - show_version = net_connect.send_command(show_ver_command) - except (NetMikoTimeoutException, NetMikoAuthenticationException) as e: - return_data[identifier] = (False, e) - - # Add data to the queue (for parent process) - mp_queue.put(return_data) - return None - - return_data[identifier] = (True, show_version) - mp_queue.put(return_data) - - -def main(): - mp_queue = multiprocessing.Queue() - processes = [] - - print "\nStart time: " + str(datetime.now()) - - for a_device in all_devices: - p = multiprocessing.Process(target=worker_show_version, args=(a_device, mp_queue)) - processes.append(p) - # start the work process - p.start() - - # retrieve all the data from the queue - results = [] - while any(p.is_alive() for p in processes): - time.sleep(0.1) - while not mp_queue.empty(): - results.append(mp_queue.get()) - - # wait until the child processes have completed - for p in processes: - p.join() - - print_output(results) - - -if __name__ == '__main__': - main() diff --git a/examples/scp_example.py b/examples/scp_example.py deleted file mode 100755 index cd5dc919a..000000000 --- a/examples/scp_example.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python -''' -Cisco IOS only - -Requires scp https://github.com/jbardin/scp.py -''' - -from netmiko import ConnectHandler, SCPConn -from SECRET_DEVICE_CREDS import cisco_881 - -def main(): - ''' - SCP transfer cisco_logging.txt to network device - - Use ssh_conn as ssh channel into network device - scp_conn must be closed after file transfer - ''' - ssh_conn = ConnectHandler(**cisco_881) - scp_conn = SCPConn(ssh_conn) - s_file = 'cisco_logging.txt' - d_file = 'cisco_logging.txt' - - print "\n\n" - - scp_conn.scp_transfer_file(s_file, d_file) - scp_conn.close() - - output = ssh_conn.send_command("show flash: | inc cisco_logging") - print ">> " + output + '\n' - - # Disable file copy confirmation - output = ssh_conn.send_config_set(["file prompt quiet"]) - - # Execute config merge - print "Performing config merge\n" - output = ssh_conn.send_command("copy flash:cisco_logging.txt running-config") - - # Verify change - print "Verifying logging buffer change" - output = ssh_conn.send_command("show run | inc logging buffer") - print ">> " + output + '\n' - - # Restore copy confirmation - output = ssh_conn.send_config_set(["file prompt alert"]) - - -if __name__ == "__main__": - main() diff --git a/examples/show_command/show_command.py b/examples/show_command/show_command.py new file mode 100755 index 000000000..51fc25d4e --- /dev/null +++ b/examples/show_command/show_command.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +from __future__ import print_function, unicode_literals + +# Netmiko is the same as ConnectHandler +from netmiko import Netmiko +from getpass import getpass + +my_device = { + 'host': 'host.domain.com', + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios', +} + +net_connect = Netmiko(**my_device) + +output = net_connect.send_command("show ip int brief") +print(output) + +net_connect.disconnect() diff --git a/examples/show_command/show_command_textfsm.py b/examples/show_command/show_command_textfsm.py new file mode 100755 index 000000000..6a3e9c783 --- /dev/null +++ b/examples/show_command/show_command_textfsm.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +from __future__ import print_function, unicode_literals + +# Netmiko is the same as ConnectHandler +from netmiko import Netmiko +from getpass import getpass + +my_device = { + 'host': 'host.domain.com', + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios', +} + +net_connect = Netmiko(**my_device) +# Requires ntc-templates to be installed in ~/ntc-templates/templates +output = net_connect.send_command("show ip int brief", use_textfsm=True) +print(output) diff --git a/examples/simple_connection/simple_conn.py b/examples/simple_connection/simple_conn.py new file mode 100755 index 000000000..535a73095 --- /dev/null +++ b/examples/simple_connection/simple_conn.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +from __future__ import print_function, unicode_literals + +# Netmiko is the same as ConnectHandler +from netmiko import Netmiko +from getpass import getpass + +net_connect = Netmiko(host='host.domain.com', username='pyclass', + password=getpass(), device_type='cisco_ios') + +print(net_connect.find_prompt()) +net_connect.disconnect() diff --git a/examples/simple_connection/simple_conn_dict.py b/examples/simple_connection/simple_conn_dict.py new file mode 100755 index 000000000..5ece4dd95 --- /dev/null +++ b/examples/simple_connection/simple_conn_dict.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +from __future__ import print_function, unicode_literals + +# Netmiko is the same as ConnectHandler +from netmiko import Netmiko +from getpass import getpass + +my_device = { + 'host': 'host.domain.com', + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios', +} + +net_connect = Netmiko(**my_device) +print(net_connect.find_prompt()) +net_connect.disconnect() diff --git a/examples/troubleshooting/enable_logging.py b/examples/troubleshooting/enable_logging.py new file mode 100755 index 000000000..ca695f581 --- /dev/null +++ b/examples/troubleshooting/enable_logging.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +from __future__ import print_function, unicode_literals + +import logging +from netmiko import Netmiko +from getpass import getpass + +# This will create a file named 'test.log' in your current directory. +# It will log all reads and writes on the SSH channel. +logging.basicConfig(filename='test.log', level=logging.DEBUG) +logger = logging.getLogger("netmiko") + +my_device = { + 'host': 'host.domain.com', + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios', +} + +net_connect = Netmiko(**my_device) +output = net_connect.send_command("show ip int brief") +print(output) +net_connect.disconnect() diff --git a/netmiko/__init__.py b/netmiko/__init__.py index e3fa88cd4..a041da3a4 100644 --- a/netmiko/__init__.py +++ b/netmiko/__init__.py @@ -16,17 +16,18 @@ from netmiko.ssh_exception import NetMikoAuthenticationException from netmiko.ssh_autodetect import SSHDetect from netmiko.base_connection import BaseConnection +from netmiko.scp_functions import file_transfer # Alternate naming NetmikoTimeoutError = NetMikoTimeoutException NetmikoAuthError = NetMikoAuthenticationException Netmiko = ConnectHandler -__version__ = '2.0.2' +__version__ = '2.1.0' __all__ = ('ConnectHandler', 'ssh_dispatcher', 'platforms', 'SCPConn', 'FileTransfer', 'NetMikoTimeoutException', 'NetMikoAuthenticationException', 'NetmikoTimeoutError', 'NetmikoAuthError', 'InLineTransfer', 'redispatch', - 'SSHDetect', 'BaseConnection', 'Netmiko') + 'SSHDetect', 'BaseConnection', 'Netmiko', 'file_transfer') # Cisco cntl-shift-six sequence CNTL_SHIFT_6 = chr(30) diff --git a/netmiko/arista/arista_ssh.py b/netmiko/arista/arista_ssh.py index f0605db34..0e21e303a 100644 --- a/netmiko/arista/arista_ssh.py +++ b/netmiko/arista/arista_ssh.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals import time -import re from netmiko.cisco_base_connection import CiscoSSHConnection from netmiko.cisco_base_connection import CiscoFileTransfer from netmiko import log @@ -35,43 +34,27 @@ def check_config_mode(self, check_string=')#', pattern=''): log.debug("check_config_mode: {0}".format(repr(output))) return check_string in output + def _enter_shell(self): + """Enter the Bourne Shell.""" + return self.send_command('bash', expect_string=r"[\$#]") + + def _return_cli(self): + """Return to the CLI.""" + return self.send_command('exit', expect_string=r"[#>]") + class AristaFileTransfer(CiscoFileTransfer): """Arista SCP File Transfer driver.""" - def __init__(self, ssh_conn, source_file, dest_file, file_system=None, direction='put'): - msg = "Arista SCP Driver is under development and not fully implemented" - raise NotImplementedError(msg) - self.ssh_ctl_chan = ssh_conn - self.source_file = source_file - self.dest_file = dest_file - self.direction = direction - - if file_system: - self.file_system = file_system - else: - raise ValueError("Destination file system must be specified for Arista") - - # if direction == 'put': - # self.source_md5 = self.file_md5(source_file) - # self.file_size = os.stat(source_file).st_size - # elif direction == 'get': - # self.source_md5 = self.remote_md5(remote_file=source_file) - # self.file_size = self.remote_file_size(remote_file=source_file) - # else: - # raise ValueError("Invalid direction specified") - - def put_file(self): - """SCP copy the file from the local system to the remote device.""" - destination = "{}/{}".format(self.file_system, self.dest_file) - self.scp_conn.scp_transfer_file(self.source_file, destination) - # Must close the SCP connection to get the file written (flush) - self.scp_conn.close() - - def remote_space_available(self, search_pattern=r"(\d+) bytes free"): + def __init__(self, ssh_conn, source_file, dest_file, file_system="/mnt/flash", direction='put'): + return super(AristaFileTransfer, self).__init__(ssh_conn=ssh_conn, + source_file=source_file, + dest_file=dest_file, + file_system=file_system, + direction=direction) + + def remote_space_available(self, search_pattern=""): """Return space available on remote device.""" - return super(AristaFileTransfer, self).remote_space_available( - search_pattern=search_pattern - ) + return self._remote_space_available_unix(search_pattern=search_pattern) def verify_space_available(self, search_pattern=r"(\d+) bytes free"): """Verify sufficient space is available on destination file system (return boolean).""" @@ -81,45 +64,22 @@ def verify_space_available(self, search_pattern=r"(\d+) bytes free"): def check_file_exists(self, remote_cmd=""): """Check if the dest_file already exists on the file system (return boolean).""" - raise NotImplementedError + return self._check_file_exists_unix(remote_cmd=remote_cmd) def remote_file_size(self, remote_cmd="", 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 = "dir {}/{}".format(self.file_system, remote_file) - - remote_out = self.ssh_ctl_chan.send_command(remote_cmd) - # Match line containing file name - escape_file_name = re.escape(remote_file) - pattern = r".*({}).*".format(escape_file_name) - match = re.search(pattern, remote_out) - 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) - - @staticmethod - def process_md5(md5_output, pattern=r"= (.*)"): - raise NotImplementedError + return self._remote_file_size_unix(remote_cmd=remote_cmd, remote_file=remote_file) - def remote_md5(self, base_cmd='show file', remote_file=None): + def remote_md5(self, base_cmd='verify /md5', remote_file=None): if remote_file is None: if self.direction == 'put': remote_file = self.dest_file elif self.direction == 'get': remote_file = self.source_file - remote_md5_cmd = "{} {}{} md5sum".format(base_cmd, self.file_system, remote_file) - return self.ssh_ctl_chan.send_command(remote_md5_cmd, delay_factor=3.0) + remote_md5_cmd = "{} file:{}/{}".format(base_cmd, self.file_system, remote_file) + dest_md5 = self.ssh_ctl_chan.send_command(remote_md5_cmd, max_loops=750, delay_factor=2) + dest_md5 = self.process_md5(dest_md5) + return dest_md5 def enable_scp(self, cmd=None): raise NotImplementedError diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index 33c62b15b..60dbc052c 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -283,7 +283,7 @@ def is_alive(self): # Try sending IAC + NOP (IAC is telnet way of sending command # IAC = Interpret as Command (it comes before the NOP) log.debug("Sending IAC + NOP") - self.device.write_channel(telnetlib.IAC + telnetlib.NOP) + self.write_channel(telnetlib.IAC + telnetlib.NOP) return True except AttributeError: return False @@ -367,7 +367,7 @@ def _read_channel_expect(self, pattern='', re_flags=0, max_loops=150): loop_delay = .1 # Default to making loop time be roughly equivalent to self.timeout (support old max_loops # argument for backwards compatibility). - if max_loops != 150: + if max_loops == 150: max_loops = self.timeout / loop_delay while i < max_loops: if self.protocol == 'ssh': @@ -1062,7 +1062,11 @@ def exit_enable_mode(self, exit_command=''): def check_config_mode(self, check_string='', pattern=''): """Checks if the device is in configuration mode or not.""" self.write_channel(self.RETURN) - output = self.read_until_pattern(pattern=pattern) + # You can encounter an issue here (on router name changes) prefer delay-based solution + if not pattern: + output = self._read_channel_timing() + else: + output = self.read_until_pattern(pattern=pattern) return check_string in output def config_mode(self, config_command='', pattern=''): diff --git a/netmiko/cisco/__init__.py b/netmiko/cisco/__init__.py index d0ccda518..61a7d5989 100644 --- a/netmiko/cisco/__init__.py +++ b/netmiko/cisco/__init__.py @@ -4,7 +4,7 @@ from netmiko.cisco.cisco_ios import InLineTransfer from netmiko.cisco.cisco_asa_ssh import CiscoAsaSSH, CiscoAsaFileTransfer from netmiko.cisco.cisco_nxos_ssh import CiscoNxosSSH, CiscoNxosFileTransfer -from netmiko.cisco.cisco_xr_ssh import CiscoXrSSH +from netmiko.cisco.cisco_xr_ssh import CiscoXrSSH, CiscoXrFileTransfer from netmiko.cisco.cisco_wlc_ssh import CiscoWlcSSH from netmiko.cisco.cisco_s300 import CiscoS300SSH from netmiko.cisco.cisco_tp_tcce import CiscoTpTcCeSSH @@ -12,4 +12,4 @@ __all__ = ['CiscoIosSSH', 'CiscoIosTelnet', 'CiscoAsaSSH', 'CiscoNxosSSH', 'CiscoXrSSH', 'CiscoWlcSSH', 'CiscoS300SSH', 'CiscoTpTcCeSSH', 'CiscoIosBase', 'CiscoIosFileTransfer', 'InLineTransfer', 'CiscoAsaFileTransfer', - 'CiscoNxosFileTransfer', 'CiscoIosSerial'] + 'CiscoNxosFileTransfer', 'CiscoIosSerial', 'CiscoXrFileTransfer'] diff --git a/netmiko/cisco/cisco_nxos_ssh.py b/netmiko/cisco/cisco_nxos_ssh.py index 0e75a7ff6..6b820177a 100644 --- a/netmiko/cisco/cisco_nxos_ssh.py +++ b/netmiko/cisco/cisco_nxos_ssh.py @@ -49,7 +49,19 @@ def __init__(self, ssh_conn, source_file, dest_file, file_system='bootflash:', d def check_file_exists(self, remote_cmd=""): """Check if the dest_file already exists on the file system (return boolean).""" - raise NotImplementedError + if self.direction == 'put': + if not remote_cmd: + remote_cmd = "dir {}{}".format(self.file_system, self.dest_file) + remote_out = self.ssh_ctl_chan.send_command_expect(remote_cmd) + search_string = r"{}.*Usage for".format(self.dest_file) + if 'No such file or directory' in remote_out: + return False + elif re.search(search_string, remote_out, flags=re.DOTALL): + 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="", remote_file=None): """Get the file size of the remote file.""" @@ -88,7 +100,7 @@ def remote_md5(self, base_cmd='show file', remote_file=None): elif self.direction == 'get': remote_file = self.source_file remote_md5_cmd = "{} {}{} md5sum".format(base_cmd, self.file_system, remote_file) - return self.ssh_ctl_chan.send_command(remote_md5_cmd, delay_factor=3.0) + return self.ssh_ctl_chan.send_command(remote_md5_cmd, max_loops=1500) def enable_scp(self, cmd=None): raise NotImplementedError diff --git a/netmiko/cisco/cisco_s300.py b/netmiko/cisco/cisco_s300.py index f183f8a3b..ee95f8af1 100644 --- a/netmiko/cisco/cisco_s300.py +++ b/netmiko/cisco/cisco_s300.py @@ -23,4 +23,5 @@ def session_preparation(self): time.sleep(.3 * self.global_delay_factor) def save_config(self, cmd='write memory', confirm=True, confirm_response='Y'): - return super(CiscoS300SSH, self).save_config(cmd=cmd, confirm=confirm) + return super(CiscoS300SSH, self).save_config(cmd=cmd, confirm=confirm, + confirm_response=confirm_response) diff --git a/netmiko/cisco/cisco_xr_ssh.py b/netmiko/cisco/cisco_xr_ssh.py index 2c2df3ba5..7ada09150 100644 --- a/netmiko/cisco/cisco_xr_ssh.py +++ b/netmiko/cisco/cisco_xr_ssh.py @@ -1,7 +1,8 @@ from __future__ import print_function from __future__ import unicode_literals import time -from netmiko.cisco_base_connection import CiscoSSHConnection +import re +from netmiko.cisco_base_connection import CiscoSSHConnection, CiscoFileTransfer class CiscoXrSSH(CiscoSSHConnection): @@ -129,3 +130,43 @@ def exit_config_mode(self, exit_config='end'): def save_config(self): """Not Implemented (use commit() method)""" raise NotImplementedError + + +class CiscoXrFileTransfer(CiscoFileTransfer): + """Cisco IOS-XR SCP File Transfer driver.""" + def process_md5(self, md5_output, pattern=r"^([a-fA-F0-9]+)$"): + """ + IOS-XR defaults with timestamps enabled + + # show md5 file /bootflash:/boot/grub/grub.cfg + Sat Mar 3 17:49:03.596 UTC + c84843f0030efd44b01343fdb8c2e801 + """ + match = re.search(pattern, md5_output, flags=re.M) + if match: + return match.group(1) + else: + raise ValueError("Invalid output from MD5 command: {}".format(md5_output)) + + def remote_md5(self, base_cmd='show md5 file', remote_file=None): + """ + IOS-XR for MD5 requires this extra leading / + + show md5 file /bootflash:/boot/grub/grub.cfg + """ + if remote_file is None: + if self.direction == 'put': + remote_file = self.dest_file + elif self.direction == 'get': + remote_file = self.source_file + # IOS-XR requires both the leading slash and the slash between file-system and file here + remote_md5_cmd = "{} /{}/{}".format(base_cmd, self.file_system, remote_file) + dest_md5 = self.ssh_ctl_chan.send_command(remote_md5_cmd, max_loops=1500) + dest_md5 = self.process_md5(dest_md5) + return dest_md5 + + def enable_scp(self, cmd=None): + raise NotImplementedError + + def disable_scp(self, cmd=None): + raise NotImplementedError diff --git a/netmiko/cisco_base_connection.py b/netmiko/cisco_base_connection.py index 31ad1eb53..344faa611 100644 --- a/netmiko/cisco_base_connection.py +++ b/netmiko/cisco_base_connection.py @@ -27,8 +27,6 @@ def check_config_mode(self, check_string=')#', pattern=''): Cisco IOS devices abbreviate the prompt at 20 chars in config mode """ - if not pattern: - pattern = re.escape(self.base_prompt[:16]) return super(CiscoBaseConnection, self).check_config_mode(check_string=check_string, pattern=pattern) @@ -157,7 +155,10 @@ def _autodetect_fs(self, cmd='dir', pattern=r'Directory of (.*)/'): # Test file_system cmd = "dir {}".format(file_system) output = self.send_command_expect(cmd) - if '% Invalid' not in output: + if '% Invalid' in output or '%Error:' in output: + raise ValueError("An error occurred in dynamically determining remote file " + "system: {} {}".format(cmd, output)) + else: return file_system raise ValueError("An error occurred in dynamically determining remote file " diff --git a/netmiko/juniper/juniper_ssh.py b/netmiko/juniper/juniper_ssh.py index b46f95d1e..52bedd8ec 100644 --- a/netmiko/juniper/juniper_ssh.py +++ b/netmiko/juniper/juniper_ssh.py @@ -2,10 +2,9 @@ import re import time -import os from netmiko.base_connection import BaseConnection -from netmiko.scp_handler import BaseFileTransfer, SCPConn +from netmiko.scp_handler import BaseFileTransfer class JuniperSSH(BaseConnection): @@ -31,6 +30,14 @@ def session_preparation(self): time.sleep(.3 * self.global_delay_factor) self.clear_buffer() + def _enter_shell(self): + """Enter the Bourne Shell.""" + return self.send_command('start shell sh', expect_string=r"[\$#]") + + def _return_cli(self): + """Return to the Juniper CLI.""" + return self.send_command('exit', expect_string=r"[#>]") + def enter_cli_mode(self): """Check if at shell prompt root@ and go into CLI.""" delay_factor = self.select_delay_factor(delay_factor=0) @@ -190,142 +197,27 @@ def strip_context_items(self, a_string): class JuniperFileTransfer(BaseFileTransfer): """Juniper SCP File Transfer driver.""" def __init__(self, ssh_conn, source_file, dest_file, file_system="/var/tmp", direction='put'): - msg = "Juniper SCP Driver is under development and not fully implemented" - raise NotImplementedError(msg) - self.ssh_ctl_chan = ssh_conn - self.dest_file = dest_file - self.direction = direction - - self.file_system = file_system - - if direction == 'put': - self.source_file = source_file - # self.source_md5 = self.file_md5(source_file) - self.file_size = os.stat(self.source_file).st_size - elif direction == 'get': - self.source_file = "{}/{}".format(file_system, source_file) - # self.source_md5 = self.remote_md5(remote_file=source_file) - self.file_size = self.remote_file_size(remote_file=self.source_file) - else: - raise ValueError("Invalid direction specified") - - def __enter__(self): - """Context manager setup""" - self.establish_scp_conn() - return self - - def __exit__(self, exc_type, exc_value, traceback): - """Context manager cleanup.""" - self.close_scp_chan() - - def establish_scp_conn(self): - """Establish SCP connection.""" - self.scp_conn = SCPConn(self.ssh_ctl_chan) - - def close_scp_chan(self): - """Close the SCP connection to the remote network device.""" - self.scp_conn.close() - self.scp_conn = None + return super(JuniperFileTransfer, self).__init__(ssh_conn=ssh_conn, + source_file=source_file, + dest_file=dest_file, + file_system=file_system, + direction=direction) def remote_space_available(self, search_pattern=""): """Return space available on remote device.""" - # Ensure at BSD prompt - self.ssh_ctl_chan.send_command('start shell sh', expect_string=r"[\$#]") - remote_cmd = "/bin/df -k {}".format(self.file_system) - remote_output = self.ssh_ctl_chan.send_command(remote_cmd, expect_string=r"[\$#]") - - # Try to ensure parsing is correct: - # Filesystem 512-blocks Used Avail Capacity Mounted on - # /dev/bo0s3f 1264808 16376 1147248 1% /cf/var - remote_output = remote_output.strip() - fields = remote_output.splitlines() - - # First line is the header; second is the actual file system info - header_line = fields[0] - filesystem_line = fields[1] - - if 'Filesystem' not in header_line or 'Avail' not in header_line.split()[3]: - # Filesystem 512-blocks Used Avail Capacity Mounted on - msg = "Parsing error, unexpected output from {}:\n{}".format(remote_cmd, - remote_output) - raise ValueError(msg) - - space_available = filesystem_line.split()[3] - if not re.search(r"^\d+$", space_available): - msg = "Parsing error, unexpected output from {}:\n{}".format(remote_cmd, - remote_output) - raise ValueError(msg) - - # Ensure back at CLI prompt - self.ssh_ctl_chan.send_command('cli', expect_string=r">") - return int(space_available) * 1024 + return self._remote_space_available_unix(search_pattern=search_pattern) def check_file_exists(self, remote_cmd=""): """Check if the dest_file already exists on the file system (return boolean).""" - if self.direction == 'put': - self.ssh_ctl_chan.send_command('start shell sh', expect_string=r"[\$#]") - remote_cmd = "ls {}/{}".format(self.file_system, self.dest_file) - remote_out = self.ssh_ctl_chan.send_command(remote_cmd, expect_string=r"[\$#]") - - # Ensure back at CLI prompt - self.ssh_ctl_chan.send_command('cli', expect_string=r">") - return self.dest_file in remote_out - - elif self.direction == 'get': - return os.path.exists(self.dest_file) + return self._check_file_exists_unix(remote_cmd=remote_cmd) def remote_file_size(self, remote_cmd="", 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 = "ls -l {}".format(remote_file) - # Ensure at BSD prompt - self.ssh_ctl_chan.send_command('start shell sh', expect_string=r"[\$#]") - remote_out = self.ssh_ctl_chan.send_command(remote_cmd, expect_string=r"[\$#]") - escape_file_name = re.escape(remote_file) - pattern = r".*({}).*".format(escape_file_name) - match = re.search(pattern, remote_out) - if match: - # Format: -rw-r--r-- 1 pyclass wheel 12 Nov 5 19:07 /var/tmp/test3.txt - line = match.group(0) - file_size = line.split()[4] - - # Ensure back at CLI prompt - self.ssh_ctl_chan.send_command('cli', expect_string=r">") - return int(file_size) - - @staticmethod - def process_md5(md5_output, pattern=r"= (.*)"): - """ - Process the string to retrieve the MD5 hash + return self._remote_file_size_unix(remote_cmd=remote_cmd, remote_file=remote_file) - Output from Cisco IOS (ASA is similar) - .MD5 of flash:file_name Done! - verify /md5 (flash:file_name) = 410db2a7015eaa42b1fe71f1bf3d59a2 - """ - raise NotImplementedError - - def compare_md5(self): - """Compare md5 of file on network device to md5 of local file""" - raise NotImplementedError - - def remote_md5(self, base_cmd='verify /md5', remote_file=None): - raise NotImplementedError - - def put_file(self): - """SCP copy the file from the local system to the remote device.""" - destination = "{}/{}".format(self.file_system, self.dest_file) - self.scp_conn.scp_transfer_file(self.source_file, destination) - # Must close the SCP connection to get the file written (flush) - self.scp_conn.close() - - def verify_file(self): - """Verify the file has been transferred correctly.""" - raise NotImplementedError + def remote_md5(self, base_cmd='file checksum md5', remote_file=None): + return super(JuniperFileTransfer, self).remote_md5(base_cmd=base_cmd, + remote_file=remote_file) def enable_scp(self, cmd=None): raise NotImplementedError diff --git a/netmiko/scp_functions.py b/netmiko/scp_functions.py new file mode 100644 index 000000000..8f4310c45 --- /dev/null +++ b/netmiko/scp_functions.py @@ -0,0 +1,94 @@ +""" +Netmiko SCP operations. + +Supports file get and file put operations. + +SCP requires a separate SSH connection for a control channel. + +Currently only supports Cisco IOS and Cisco ASA. +""" +from __future__ import print_function +from __future__ import unicode_literals + +from netmiko import FileTransfer, InLineTransfer + + +def verifyspace_and_transferfile(scp_transfer): + """Verify space and transfer file.""" + if not scp_transfer.verify_space_available(): + raise ValueError("Insufficient space available on remote device") + scp_transfer.transfer_file() + + +def file_transfer(ssh_conn, source_file, dest_file, file_system=None, direction='put', + disable_md5=False, inline_transfer=False, overwrite_file=False): + """Use Secure Copy or Inline (IOS-only) to transfer files to/from network devices. + + inline_transfer ONLY SUPPORTS TEXT FILES and will not support binary file transfers. + + return { + 'file_exists': boolean, + 'file_transferred': boolean, + 'file_verified': boolean, + } + """ + transferred_and_verified = { + 'file_exists': True, + 'file_transferred': True, + 'file_verified': True, + } + transferred_and_notverified = { + 'file_exists': True, + 'file_transferred': True, + 'file_verified': False, + } + nottransferred_but_verified = { + 'file_exists': True, + 'file_transferred': False, + 'file_verified': True, + } + + if 'cisco_ios' in ssh_conn.device_type or 'cisco_xe' in ssh_conn.device_type: + cisco_ios = True + else: + cisco_ios = False + if not cisco_ios and inline_transfer: + raise ValueError("Inline Transfer only supported for Cisco IOS/Cisco IOS-XE") + + TransferClass = InLineTransfer if inline_transfer else FileTransfer + with TransferClass(ssh_conn, source_file=source_file, dest_file=dest_file, + file_system=file_system, direction=direction) as scp_transfer: + + if scp_transfer.check_file_exists(): + if overwrite_file: + if not disable_md5: + if scp_transfer.compare_md5(): + 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(): + return transferred_and_verified + else: + raise ValueError("MD5 failure between source and destination files") + else: + # File exists, you can overwrite it, but MD5 not allowed (transfer file) + verifyspace_and_transferfile(scp_transfer) + return transferred_and_notverified + else: + # File exists, but you can't overwrite it. + if not disable_md5: + if scp_transfer.compare_md5(): + 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(): + return transferred_and_verified + else: + raise ValueError("MD5 failure between source and destination files") + else: + return transferred_and_notverified diff --git a/netmiko/scp_handler.py b/netmiko/scp_handler.py index 5e6c2ca53..bf05f091b 100644 --- a/netmiko/scp_handler.py +++ b/netmiko/scp_handler.py @@ -59,8 +59,14 @@ def __init__(self, ssh_conn, source_file, dest_file, file_system=None, direction self.dest_file = dest_file self.direction = direction + auto_flag = 'cisco_ios' in ssh_conn.device_type or \ + 'cisco_xe' in ssh_conn.device_type or \ + 'cisco_xr' in ssh_conn.device_type if not file_system: - self.file_system = self.ssh_ctl_chan._autodetect_fs() + if auto_flag: + self.file_system = self.ssh_ctl_chan._autodetect_fs() + else: + raise ValueError("Destination file system not specified") else: self.file_system = file_system @@ -98,6 +104,37 @@ def remote_space_available(self, search_pattern=r"(\d+) bytes free"): match = re.search(search_pattern, remote_output) return int(match.group(1)) + def _remote_space_available_unix(self, search_pattern=""): + """Return space available on *nix system (BSD/Linux).""" + self.ssh_ctl_chan._enter_shell() + remote_cmd = "/bin/df -k {}".format(self.file_system) + remote_output = self.ssh_ctl_chan.send_command(remote_cmd, expect_string=r"[\$#]") + + # Try to ensure parsing is correct: + # Filesystem 512-blocks Used Avail Capacity Mounted on + # /dev/bo0s3f 1264808 16376 1147248 1% /cf/var + remote_output = remote_output.strip() + output_lines = remote_output.splitlines() + + # First line is the header; second is the actual file system info + header_line = output_lines[0] + filesystem_line = output_lines[1] + + if 'Filesystem' not in header_line or 'Avail' not in header_line.split()[3]: + # Filesystem 512-blocks Used Avail Capacity Mounted on + msg = "Parsing error, unexpected output from {}:\n{}".format(remote_cmd, + remote_output) + raise ValueError(msg) + + space_available = filesystem_line.split()[3] + if not re.search(r"^\d+$", space_available): + msg = "Parsing error, unexpected output from {}:\n{}".format(remote_cmd, + remote_output) + raise ValueError(msg) + + self.ssh_ctl_chan._return_cli() + return int(space_available) * 1024 + def local_space_available(self): """Return space available on local filesystem.""" destination_stats = os.statvfs(".") @@ -117,18 +154,29 @@ def check_file_exists(self, remote_cmd=""): """Check if the dest_file already exists on the file system (return boolean).""" if self.direction == 'put': if not remote_cmd: - remote_cmd = "dir {0}/{1}".format(self.file_system, self.dest_file) + remote_cmd = "dir {}/{}".format(self.file_system, self.dest_file) remote_out = self.ssh_ctl_chan.send_command_expect(remote_cmd) search_string = r"Directory of .*{0}".format(self.dest_file) - if 'Error opening' in remote_out: + if 'Error opening' in remote_out or 'No such file or directory' in remote_out: return False - elif re.search(search_string, remote_out): + elif re.search(search_string, remote_out, flags=re.DOTALL): return True else: raise ValueError("Unexpected output from check_file_exists") elif self.direction == 'get': return os.path.exists(self.dest_file) + def _check_file_exists_unix(self, remote_cmd=""): + """Check if the dest_file already exists on the file system (return boolean).""" + if self.direction == 'put': + self.ssh_ctl_chan._enter_shell() + remote_cmd = "ls {}".format(self.file_system) + remote_out = self.ssh_ctl_chan.send_command(remote_cmd, expect_string=r"[\$#]") + self.ssh_ctl_chan._return_cli() + return self.dest_file in remote_out + elif self.direction == 'get': + return os.path.exists(self.dest_file) + def remote_file_size(self, remote_cmd="", remote_file=None): """Get the file size of the remote file.""" if remote_file is None: @@ -150,11 +198,35 @@ def remote_file_size(self, remote_cmd="", remote_file=None): line = match.group(0) # Format will be 26 -rw- 6738 Jul 30 2016 19:49:50 -07:00 filename file_size = line.split()[2] - if 'Error opening' in remote_out: + if 'Error opening' in remote_out or 'No such file or directory' in remote_out: raise IOError("Unable to find file on remote system") else: return int(file_size) + def _remote_file_size_unix(self, remote_cmd="", 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 + remote_file = "{}/{}".format(self.file_system, remote_file) + if not remote_cmd: + remote_cmd = "ls -l {}".format(remote_file) + + self.ssh_ctl_chan._enter_shell() + remote_out = self.ssh_ctl_chan.send_command(remote_cmd, expect_string=r"[\$#]") + escape_file_name = re.escape(remote_file) + pattern = r"^.* ({}).*$".format(escape_file_name) + match = re.search(pattern, remote_out, flags=re.M) + if match: + # Format: -rw-r--r-- 1 pyclass wheel 12 Nov 5 19:07 /var/tmp/test3.txt + line = match.group(0) + file_size = line.split()[4] + + self.ssh_ctl_chan._return_cli() + return int(file_size) + def file_md5(self, file_name): """Compute MD5 hash of file.""" with open(file_name, "rb") as f: @@ -163,7 +235,7 @@ def file_md5(self, file_name): return file_hash @staticmethod - def process_md5(md5_output, pattern=r"= (.*)"): + def process_md5(md5_output, pattern=r"=\s+(\S+)"): """ Process the string to retrieve the MD5 hash @@ -175,7 +247,7 @@ def process_md5(md5_output, pattern=r"= (.*)"): if match: return match.group(1) else: - raise ValueError("Invalid output from MD5 command: {0}".format(md5_output)) + raise ValueError("Invalid output from MD5 command: {}".format(md5_output)) def compare_md5(self): """Compare md5 of file on network device to md5 of local file.""" @@ -196,8 +268,8 @@ def remote_md5(self, base_cmd='verify /md5', remote_file=None): remote_file = self.dest_file elif self.direction == 'get': remote_file = self.source_file - remote_md5_cmd = "{0} {1}{2}".format(base_cmd, self.file_system, remote_file) - dest_md5 = self.ssh_ctl_chan.send_command(remote_md5_cmd, delay_factor=3.0) + remote_md5_cmd = "{} {}/{}".format(base_cmd, self.file_system, remote_file) + dest_md5 = self.ssh_ctl_chan.send_command(remote_md5_cmd, max_loops=1500) dest_md5 = self.process_md5(dest_md5) return dest_md5 @@ -210,14 +282,13 @@ def transfer_file(self): def get_file(self): """SCP copy the file from the remote device to local system.""" - self.scp_conn.scp_get_file(self.source_file, self.dest_file) + source_file = "{}/{}".format(self.file_system, self.source_file) + self.scp_conn.scp_get_file(source_file, self.dest_file) self.scp_conn.close() def put_file(self): """SCP copy the file from the local system to the remote device.""" - destination = "{}{}".format(self.file_system, self.dest_file) - if ':' not in destination: - raise ValueError("Invalid destination file system specified") + destination = "{}/{}".format(self.file_system, self.dest_file) self.scp_conn.scp_transfer_file(self.source_file, destination) # Must close the SCP connection to get the file written (flush) self.scp_conn.close() diff --git a/netmiko/ssh_dispatcher.py b/netmiko/ssh_dispatcher.py index de3b5b58e..0737971f1 100644 --- a/netmiko/ssh_dispatcher.py +++ b/netmiko/ssh_dispatcher.py @@ -6,7 +6,7 @@ from netmiko.alcatel import AlcatelAosSSH from netmiko.alcatel import AlcatelSrosSSH from netmiko.arista import AristaSSH -# from netmiko.arista import AristaFileTransfer +from netmiko.arista import AristaFileTransfer from netmiko.aruba import ArubaSSH from netmiko.avaya import AvayaErsSSH from netmiko.avaya import AvayaVspSSH @@ -22,7 +22,7 @@ from netmiko.cisco import CiscoS300SSH from netmiko.cisco import CiscoTpTcCeSSH from netmiko.cisco import CiscoWlcSSH -from netmiko.cisco import CiscoXrSSH +from netmiko.cisco import CiscoXrSSH, CiscoXrFileTransfer from netmiko.coriant import CoriantSSH from netmiko.dell import DellForce10SSH from netmiko.dell import DellPowerConnectSSH @@ -37,7 +37,7 @@ from netmiko.hp import HPProcurveSSH, HPComwareSSH from netmiko.huawei import HuaweiSSH, HuaweiVrpv8SSH from netmiko.juniper import JuniperSSH -# from netmiko.juniper import JuniperFileTransfer +from netmiko.juniper import JuniperFileTransfer from netmiko.linux import LinuxSSH from netmiko.mellanox import MellanoxSSH from netmiko.mrv import MrvOptiswitchSSH @@ -112,12 +112,13 @@ } FILE_TRANSFER_MAP = { - # 'arista_eos': AristaFileTransfer, + 'arista_eos': AristaFileTransfer, 'cisco_asa': CiscoAsaFileTransfer, 'cisco_ios': CiscoIosFileTransfer, - 'cisco_xe': CiscoIosFileTransfer, 'cisco_nxos': CiscoNxosFileTransfer, - # 'juniper_junos': JuniperFileTransfer, + 'cisco_xe': CiscoIosFileTransfer, + 'cisco_xr': CiscoXrFileTransfer, + 'juniper_junos': JuniperFileTransfer, } # Also support keys that end in _ssh diff --git a/release_process.txt b/release_process.txt index 12ec4a936..1586c9014 100644 --- a/release_process.txt +++ b/release_process.txt @@ -3,7 +3,7 @@ # Make sure you have rolled the version in __init__.py -# Merge into master / checkout master +# Merge into master / checkout master (use PR in GitHub for this) # Check FIX issues in _release.sh @@ -11,5 +11,5 @@ # Create a tag for the version $ git tag -a v1.4.1 -m "Version 1.4.1 Release" -$ git push --tags +$ git push origin diff --git a/setup.cfg b/setup.cfg index ee8cc0028..8ba5463ba 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [pylama] linters = mccabe,pep8,pyflakes ignore = D203,C901 -skip = tests/*,examples/*,build/*,.tox/*,netmiko/_textfsm/* +skip = tests/*,build/*,.tox/*,netmiko/_textfsm/* [pylama:pep8] max_line_length = 100 diff --git a/tests/conftest.py b/tests/conftest.py index 9dfee05aa..f40bc8af9 100755 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -71,16 +71,34 @@ def commands(request): commands_yml = parse_yaml(PWD + "/etc/commands.yml") return commands_yml[test_platform] +def delete_file_nxos(ssh_conn, dest_file_system, dest_file): + """ + nxos1# delete bootflash:test2.txt + Do you want to delete "/test2.txt" ? (yes/no/abort) [y] y + """ + if not dest_file_system: + raise ValueError("Invalid file system specified") + if not dest_file: + raise ValueError("Invalid dest file specified") + + full_file_name = "{}{}".format(dest_file_system, dest_file) + + cmd = "delete {}".format(full_file_name) + output = ssh_conn.send_command_timing(cmd) + if 'yes/no/abort' in output and dest_file in output: + output += ssh_conn.send_command_timing("y", strip_command=False, strip_prompt=False) + return output + else: + output += ssh_conn.send_command_timing("abort") + raise ValueError("An error happened deleting file on Cisco NX-OS") def delete_file_ios(ssh_conn, dest_file_system, dest_file): """Delete a remote file for a Cisco IOS device.""" - if not dest_file_system: raise ValueError("Invalid file system specified") if not dest_file: raise ValueError("Invalid dest file specified") - # Check if the dest_file already exists full_file_name = "{0}/{1}".format(dest_file_system, dest_file) cmd = "delete {0}".format(full_file_name) @@ -96,6 +114,16 @@ def delete_file_ios(ssh_conn, dest_file_system, dest_file): raise ValueError("An error happened deleting file on Cisco IOS") +def delete_file_generic(ssh_conn, dest_file_system, dest_file): + """Delete a remote file for a Junos device.""" + full_file_name = "{}/{}".format(dest_file_system, dest_file) + cmd = "rm {}".format(full_file_name) + output = ssh_conn._enter_shell() + output += ssh_conn.send_command_timing(cmd, strip_command=False, strip_prompt=False) + output += ssh_conn._return_cli() + return output + + @pytest.fixture(scope='module') def scp_fixture(request): """ @@ -103,13 +131,53 @@ def scp_fixture(request): Return a tuple (ssh_conn, scp_handle) """ + platform_args = { + 'cisco_ios': { + 'file_system': 'flash:', + 'enable_scp': True, + 'delete_file': delete_file_ios, + }, + 'juniper_junos': { + 'file_system': '/var/tmp', + 'enable_scp': False, + 'delete_file': delete_file_generic, + }, + 'arista_eos': { + 'file_system': '/mnt/flash', + 'enable_scp': False, + 'delete_file': delete_file_generic, + }, + 'cisco_nxos': { + 'file_system': 'bootflash:', + 'enable_scp': False, + 'delete_file': delete_file_nxos, + }, + 'cisco_xr': { + 'file_system': 'disk0:', + 'enable_scp': False, + # Delete pattern is the same on IOS-XR + 'delete_file': delete_file_ios, + }, + } + + # Create the files + with open("test9.txt", "w") as f: + # Not important what it is in the file + f.write("no logging console\n") + + with open("test2_src.txt", "w") as f: + # Not important what it is in the file + f.write("no logging console\n") + f.write("logging buffered 10000\n") + device_under_test = request.config.getoption('test_device') test_devices = parse_yaml(PWD + "/etc/test_devices.yml") device = test_devices[device_under_test] device['verbose'] = False ssh_conn = ConnectHandler(**device) - dest_file_system = 'flash:' + platform = device['device_type'] + dest_file_system = platform_args[platform]['file_system'] source_file = 'test9.txt' dest_file = 'test9.txt' local_file = 'testx.txt' @@ -120,14 +188,15 @@ def scp_fixture(request): scp_transfer.establish_scp_conn() # Make sure SCP is enabled - scp_transfer.enable_scp() + if platform_args[platform]['enable_scp']: + scp_transfer.enable_scp() # Delete the test transfer files if scp_transfer.check_file_exists(): - delete_file_ios(ssh_conn, dest_file_system, dest_file) + func = platform_args[platform]['delete_file'] + func(ssh_conn, dest_file_system, dest_file) if os.path.exists(local_file): os.remove(local_file) - return (ssh_conn, scp_transfer) @pytest.fixture(scope='module') @@ -137,13 +206,43 @@ def scp_fixture_get(request): Return a tuple (ssh_conn, scp_handle) """ + platform_args = { + 'cisco_ios': { + 'file_system': 'flash:', + 'enable_scp': True, + 'delete_file': delete_file_ios, + }, + 'juniper_junos': { + 'file_system': '/var/tmp', + 'enable_scp': False, + 'delete_file': delete_file_generic, + }, + 'arista_eos': { + 'file_system': '/mnt/flash', + 'enable_scp': False, + 'delete_file': delete_file_generic, + }, + 'cisco_nxos': { + 'file_system': 'bootflash:', + 'enable_scp': False, + 'delete_file': delete_file_nxos, + }, + 'cisco_xr': { + 'file_system': 'disk0:', + 'enable_scp': False, + # Delete pattern is the same on IOS-XR + 'delete_file': delete_file_ios, + }, + } + device_under_test = request.config.getoption('test_device') test_devices = parse_yaml(PWD + "/etc/test_devices.yml") device = test_devices[device_under_test] device['verbose'] = False ssh_conn = ConnectHandler(**device) - dest_file_system = 'flash:' + platform = device['device_type'] + dest_file_system = platform_args[platform]['file_system'] source_file = 'test9.txt' local_file = 'testx.txt' dest_file = local_file @@ -154,12 +253,12 @@ def scp_fixture_get(request): scp_transfer.establish_scp_conn() # Make sure SCP is enabled - scp_transfer.enable_scp() + if platform_args[platform]['enable_scp']: + scp_transfer.enable_scp() # Delete the test transfer files if os.path.exists(local_file): os.remove(local_file) - return (ssh_conn, scp_transfer) @pytest.fixture(scope='module') @@ -169,6 +268,11 @@ def tcl_fixture(request): Return a tuple (ssh_conn, tcl_handle) """ + # Create the files + with open("test9.txt", "w") as f: + # Not important what it is in the file + f.write("no logging console\n") + device_under_test = request.config.getoption('test_device') test_devices = parse_yaml(PWD + "/etc/test_devices.yml") device = test_devices[device_under_test] @@ -206,3 +310,78 @@ def ssh_autodetect(request): device['device_type'] = 'autodetect' conn = SSHDetect(**device) return (conn, my_device_type) + +@pytest.fixture(scope='module') +def scp_file_transfer(request): + """ + Testing file_transfer + + Return the netmiko connection object + """ + platform_args = { + 'cisco_ios': { + 'file_system': 'flash:', + 'enable_scp': True, + 'delete_file': delete_file_ios, + }, + 'juniper_junos': { + 'file_system': '/var/tmp', + 'enable_scp': False, + 'delete_file': delete_file_generic, + }, + 'arista_eos': { + 'file_system': '/mnt/flash', + 'enable_scp': False, + 'delete_file': delete_file_generic, + }, + 'cisco_nxos': { + 'file_system': 'bootflash:', + 'enable_scp': False, + 'delete_file': delete_file_nxos, + }, + 'cisco_xr': { + 'file_system': 'disk0:', + 'enable_scp': False, + # Delete pattern is the same on IOS-XR + 'delete_file': delete_file_ios, + }, + } + + # Create the files + with open("test9.txt", "w") as f: + # Not important what it is in the file + f.write("no logging console\n") + + with open("test2_src.txt", "w") as f: + # Not important what it is in the file + f.write("no logging console\n") + f.write("logging buffered 10000\n") + + device_under_test = request.config.getoption('test_device') + test_devices = parse_yaml(PWD + "/etc/test_devices.yml") + device = test_devices[device_under_test] + device['verbose'] = False + ssh_conn = ConnectHandler(**device) + + platform = device['device_type'] + file_system = platform_args[platform]['file_system'] + source_file = 'test9.txt' + dest_file = 'test9.txt' + local_file = 'testx.txt' + alt_file = 'test2.txt' + direction = 'put' + + scp_transfer = FileTransfer(ssh_conn, source_file=source_file, dest_file=dest_file, + file_system=file_system, direction=direction) + scp_transfer.establish_scp_conn() + + # Delete the test transfer files + if scp_transfer.check_file_exists(): + func = platform_args[platform]['delete_file'] + func(ssh_conn, file_system, dest_file) + if os.path.exists(local_file): + os.remove(local_file) + if os.path.exists(alt_file): + os.remove(alt_file) + + return (ssh_conn, file_system) diff --git a/tests/test2_src.txt b/tests/test2_src.txt new file mode 100644 index 000000000..d2ddd3cb8 --- /dev/null +++ b/tests/test2_src.txt @@ -0,0 +1,2 @@ +no logging console +logging buffered 10000 diff --git a/tests/test_netmiko_scp.py b/tests/test_netmiko_scp.py index 3356ab75a..ad5c938fb 100755 --- a/tests/test_netmiko_scp.py +++ b/tests/test_netmiko_scp.py @@ -4,34 +4,35 @@ import time import sys import os +import pytest from datetime import datetime from getpass import getpass from netmiko import ConnectHandler, FileTransfer +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_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 - if scp_transfer.check_file_exists(): assert False else: scp_transfer.put_file() assert scp_transfer.check_file_exists() == True -def test_remote_space_available(scp_fixture): +def test_remote_space_available(scp_fixture, expected_responses): ssh_conn, scp_transfer = scp_fixture remote_space = scp_transfer.remote_space_available() - assert remote_space >= 30000000 + assert remote_space >= expected_responses["scp_remote_space"] def test_local_space_available(scp_fixture): ssh_conn, scp_transfer = scp_fixture @@ -42,18 +43,13 @@ def test_verify_space_available_put(scp_fixture): ssh_conn, scp_transfer = scp_fixture assert scp_transfer.verify_space_available() == True # intentional make there not be enough space available - scp_transfer.file_size = 1000000000 - assert scp_transfer.verify_space_available() == False - -def test_verify_space_available_get(scp_fixture_get): - ssh_conn, scp_transfer = scp_fixture_get - assert scp_transfer.verify_space_available() == True - # intentional make there not be enough space available - scp_transfer.file_size = 100000000000 + scp_transfer.file_size = 10000000000 assert scp_transfer.verify_space_available() == False def test_remote_file_size(scp_fixture): ssh_conn, scp_transfer = scp_fixture + if not scp_transfer.check_file_exists(): + scp_transfer.put_file() remote_file_size = scp_transfer.remote_file_size() assert remote_file_size == 19 @@ -65,6 +61,18 @@ def test_md5_methods(scp_fixture): assert remote_md5 == md5_value assert scp_transfer.compare_md5() == True +def test_disconnect(scp_fixture): + """Terminate the SSH session.""" + ssh_conn, scp_transfer = scp_fixture + ssh_conn.disconnect() + +def test_verify_space_available_get(scp_fixture_get): + ssh_conn, scp_transfer = scp_fixture_get + assert scp_transfer.verify_space_available() == True + # intentional make there not be enough space available + scp_transfer.file_size = 100000000000 + assert scp_transfer.verify_space_available() == False + def test_scp_get(scp_fixture_get): ssh_conn, scp_transfer = scp_fixture_get @@ -74,9 +82,9 @@ def test_scp_get(scp_fixture_get): else: scp_transfer.get_file() if scp_transfer.check_file_exists(): - assert True == True + assert True else: - assert False == True + assert False def test_md5_methods_get(scp_fixture_get): ssh_conn, scp_transfer = scp_fixture_get @@ -85,12 +93,106 @@ def test_md5_methods_get(scp_fixture_get): assert local_md5 == md5_value assert scp_transfer.compare_md5() == True -def test_disconnect(scp_fixture): - """Terminate the SSH session.""" - ssh_conn, scp_transfer = scp_fixture - ssh_conn.disconnect() - def test_disconnect_get(scp_fixture_get): """Terminate the SSH session.""" ssh_conn, scp_transfer = scp_fixture_get ssh_conn.disconnect() + + +def test_file_transfer(scp_file_transfer): + """Test Netmiko file_transfer function.""" + ssh_conn, file_system = scp_file_transfer + source_file = 'test9.txt' + dest_file = 'test9.txt' + direction = 'put' + print(file_system) + + transfer_dict = file_transfer(ssh_conn, source_file=source_file, + dest_file=dest_file, + file_system=file_system, direction=direction, + overwrite_file=True) + + # No file on device at the beginning + assert transfer_dict['file_exists'] and transfer_dict['file_transferred'] and transfer_dict['file_verified'] + + # File exists on device at this point + transfer_dict = file_transfer(ssh_conn, source_file=source_file, + dest_file=dest_file, + file_system=file_system, direction=direction, + overwrite_file=True) + assert transfer_dict['file_exists'] and not transfer_dict['file_transferred'] and transfer_dict['file_verified'] + + # Don't allow a file overwrite (switch the source file, but same dest file name) + source_file = 'test2_src.txt' + with pytest.raises(Exception): + transfer_dict = file_transfer(ssh_conn, source_file=source_file, + dest_file=dest_file, + file_system=file_system, direction=direction, + overwrite_file=False) + + # Don't allow MD5 and file overwrite not allowed + source_file = 'test9.txt' + with pytest.raises(Exception): + transfer_dict = file_transfer(ssh_conn, source_file=source_file, + dest_file=dest_file, disable_md5=True, + file_system=file_system, direction=direction, + overwrite_file=False) + + # Don't allow MD5 (this will force a re-transfer) + transfer_dict = file_transfer(ssh_conn, source_file=source_file, + dest_file=dest_file, disable_md5=True, + file_system=file_system, direction=direction, + overwrite_file=True) + assert transfer_dict['file_exists'] and transfer_dict['file_transferred'] and not transfer_dict['file_verified'] + + # Transfer 'test2.txt' in preparation for get operations + source_file = 'test2_src.txt' + dest_file = 'test2.txt' + transfer_dict = file_transfer(ssh_conn, source_file=source_file, + dest_file=dest_file, + file_system=file_system, direction=direction, + overwrite_file=True) + assert transfer_dict['file_exists'] + + # GET Operations + direction = 'get' + source_file = 'test9.txt' + dest_file = 'testx.txt' + transfer_dict = file_transfer(ssh_conn, source_file=source_file, + dest_file=dest_file, disable_md5=False, + file_system=file_system, direction=direction, + overwrite_file=True) + # File get should occur here + assert transfer_dict['file_exists'] and transfer_dict['file_transferred'] and transfer_dict['file_verified'] + + # File should exist now + transfer_dict = file_transfer(ssh_conn, source_file=source_file, + dest_file=dest_file, disable_md5=False, + file_system=file_system, direction=direction, + overwrite_file=True) + assert transfer_dict['file_exists'] and not transfer_dict['file_transferred'] and transfer_dict['file_verified'] + + # Don't allow a file overwrite (switch the file, but same dest file name) + source_file = 'test2.txt' + dest_file = 'testx.txt' + with pytest.raises(Exception): + transfer_dict = file_transfer(ssh_conn, source_file=source_file, + dest_file=dest_file, + file_system=file_system, direction=direction, + overwrite_file=False) + + # Don't allow MD5 and file overwrite not allowed + source_file = 'test9.txt' + dest_file = 'testx.txt' + with pytest.raises(Exception): + transfer_dict = file_transfer(ssh_conn, source_file=source_file, + dest_file=dest_file, disable_md5=True, + file_system=file_system, direction=direction, + overwrite_file=False) + + # Don't allow MD5 (this will force a re-transfer) + transfer_dict = file_transfer(ssh_conn, source_file=source_file, + dest_file=dest_file, disable_md5=True, + file_system=file_system, direction=direction, + overwrite_file=True) + assert transfer_dict['file_exists'] and transfer_dict['file_transferred'] and not transfer_dict['file_verified'] diff --git a/tests/test_scp.sh b/tests/test_scp.sh new file mode 100755 index 000000000..0cebead60 --- /dev/null +++ b/tests/test_scp.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +RETURN_CODE=0 + +echo "Starting tests...good luck:" \ +&& echo "SCP Tests" \ +&& py.test test_netmiko_scp.py::test_file_transfer --test_device cisco881 \ +&& py.test test_netmiko_scp.py::test_file_transfer --test_device nxos1 \ +&& py.test -s -v test_netmiko_scp.py::test_file_transfer --test_device arista_sw4 \ +&& py.test -s -v test_netmiko_scp.py::test_file_transfer --test_device juniper_srx \ +&& py.test -s -v test_netmiko_scp.py::test_file_transfer --test_device cisco_xrv \ +|| RETURN_CODE=1 + +exit $RETURN_CODE diff --git a/tests/test_suite_alt.sh b/tests/test_suite_alt.sh index 77a48fc92..ed5f43986 100755 --- a/tests/test_suite_alt.sh +++ b/tests/test_suite_alt.sh @@ -29,6 +29,7 @@ echo "Starting tests...good luck:" \ && py.test -v test_netmiko_config.py --test_device cisco_s300 \ \ && echo "Arista" \ +&& py.test -v test_netmiko_scp.py --test_device arista_sw4 \ && py.test -v test_netmiko_show.py --test_device arista_sw4 \ && py.test -v test_netmiko_config.py --test_device arista_sw4 \ \ @@ -41,6 +42,7 @@ echo "Starting tests...good luck:" \ && py.test -v test_netmiko_config.py --test_device hp_comware \ \ && echo "Juniper" \ +&& py.test -v test_netmiko_scp.py --test_device juniper_srx \ && py.test -v test_netmiko_show.py --test_device juniper_srx \ && py.test -v test_netmiko_config.py --test_device juniper_srx \ && py.test -v test_netmiko_commit.py --test_device juniper_srx \ @@ -52,11 +54,13 @@ echo "Starting tests...good luck:" \ && py.test -v test_netmiko_config.py --test_device cisco_asa_login \ \ && echo "Cisco IOS-XR" \ +&& py.test -v test_netmiko_scp.py --test_device cisco_xrv \ && py.test -v test_netmiko_show.py --test_device cisco_xrv \ && py.test -v test_netmiko_config.py --test_device cisco_xrv \ && py.test -v test_netmiko_commit.py --test_device cisco_xrv \ \ && echo "Cisco NXOS" \ +&& py.test -v test_netmiko_scp.py --test_device nxos1 \ && py.test -v test_netmiko_show.py --test_device nxos1 \ && py.test -v test_netmiko_config.py --test_device nxos1 \ \ diff --git a/tests/test_suite_tmp.sh b/tests/test_suite_tmp.sh index ac70110a5..a283e368c 100755 --- a/tests/test_suite_tmp.sh +++ b/tests/test_suite_tmp.sh @@ -16,6 +16,10 @@ echo "Starting tests...good luck:" \ && py.test -v test_netmiko_show.py --test_device cisco881 \ && py.test -v test_netmiko_config.py --test_device cisco881 \ \ +&& echo "Cisco IOS using SSH config with SSH Proxy" \ +&& py.test -v test_netmiko_show.py --test_device cisco881_ssh_config \ +&& py.test -v test_netmiko_config.py --test_device cisco881_ssh_config \ +\ && echo "Cisco IOS telnet" \ && py.test -v test_netmiko_show.py --test_device cisco881_telnet \ && py.test -v test_netmiko_config.py --test_device cisco881_telnet \ @@ -25,6 +29,7 @@ echo "Starting tests...good luck:" \ && py.test -v test_netmiko_config.py --test_device cisco_s300 \ \ && echo "Arista" \ +&& py.test -v test_netmiko_scp.py --test_device arista_sw4 \ && py.test -v test_netmiko_show.py --test_device arista_sw4 \ && py.test -v test_netmiko_config.py --test_device arista_sw4 \ \ @@ -33,6 +38,7 @@ echo "Starting tests...good luck:" \ && py.test -v test_netmiko_config.py --test_device hp_procurve \ \ && echo "Juniper" \ +&& py.test -v test_netmiko_scp.py --test_device juniper_srx \ && py.test -v test_netmiko_show.py --test_device juniper_srx \ && py.test -v test_netmiko_config.py --test_device juniper_srx \ && py.test -v test_netmiko_commit.py --test_device juniper_srx \ @@ -44,14 +50,19 @@ echo "Starting tests...good luck:" \ && py.test -v test_netmiko_config.py --test_device cisco_asa_login \ \ && echo "Cisco IOS-XR" \ +&& py.test -v test_netmiko_scp.py --test_device cisco_xrv \ && py.test -v test_netmiko_show.py --test_device cisco_xrv \ && py.test -v test_netmiko_config.py --test_device cisco_xrv \ && py.test -v test_netmiko_commit.py --test_device cisco_xrv \ \ && echo "Cisco NXOS" \ +&& py.test -v test_netmiko_scp.py --test_device nxos1 \ && py.test -v test_netmiko_show.py --test_device nxos1 \ && py.test -v test_netmiko_config.py --test_device nxos1 \ \ +&& echo "Linux SSH (using keys)" \ +&& py.test -s -v test_netmiko_show.py --test_device linux_srv1 \ +\ && echo "Autodetect tests" \ && py.test -s -v test_netmiko_autodetect.py --test_device cisco881 \ && py.test -s -v test_netmiko_autodetect.py --test_device arista_sw4 \ @@ -62,7 +73,3 @@ echo "Starting tests...good luck:" \ || RETURN_CODE=1 exit $RETURN_CODE - -#&& echo "Linux SSH (using keys)" \ -#&& py.test -s -v test_netmiko_show.py --test_device linux_srv1 \ -#\