diff --git a/README.md b/README.md index 580c41c3ff58c1e894dc96a1e1be755429aae41a..0023ba53537c1e08b06bb3b6f33ca5297746fedb 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Cisco IOS-XE Cisco IOS-XR Cisco NX-OS Cisco SG300 +Dell OS10 HP Comware7 HP ProCurve Juniper Junos @@ -58,9 +59,11 @@ A10 Accedian Aruba Ciena SAOS +Citrix Netscaler Cisco Telepresence Check Point GAiA Coriant +Dell Isilon Eltex Enterasys Extreme EXOS diff --git a/examples/configuration_changes/config_changes_to_device_list.py b/examples/configuration_changes/config_changes_to_device_list.py new file mode 100644 index 0000000000000000000000000000000000000000..b1f102b34b8b8504181d02954fd728d7205d889e --- /dev/null +++ b/examples/configuration_changes/config_changes_to_device_list.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# Author: Peter Bruno +# Purpose: Script commands to group of Cisco devices with +# success/failure feedback. +from __future__ import print_function, unicode_literals +import sys +from netmiko import ConnectHandler +from getpass import getpass + + +def usage(ext): + # exit with description and command line example + print("\nInput file should contain list of switch IP addresses.") + print("Commands should be the commands you wish to run on your") + print('network devices enclosed in "quotes".') + print( + "Results key: # = enable mode, * = successful command", + "w = write mem, ! = command failure" + ) + print("\nusage:") + print( + ("\n%s <input file>" % sys.argv[0]), + '"command1"', + '"command2"', + '"command3"', + "wr", + ) + sys.exit(ext) + + +def get_cmd_line(): + if len(sys.argv) < 2: + usage(0) + cmdlist = sys.argv[2:] + try: + with open(sys.argv[1], "r") as f: + switchip = f.read().splitlines() + f.close() + except (IndexError, IOError): + usage(0) + return switchip, cmdlist + + +def main(): + inputfile, config_commands = get_cmd_line() + + print("Switch configuration updater. Please provide login information.\n") + # Get username and password information. + username = input("Username: ") + password = getpass("Password: ") + enasecret = getpass("Enable Secret: ") + + print("{}{:<20}{:<40}{:<20}".format( + "\n", "IP Address", "Name", "Results"), end="") + + for switchip in inputfile: + ciscosw = { + "device_type": "cisco_ios", + "ip": switchip.strip(), + "username": username.strip(), + "password": password.strip(), + "secret": enasecret.strip(), + } + print() + print("{:<20}".format(switchip.strip()), end="", flush=True) + try: + # Connect to switch and enter enable mode. + net_connect = ConnectHandler(**ciscosw) + except Exception: + print("** Failed to connect.", end="", flush=True) + continue + + prompt = net_connect.find_prompt() + # Print out the prompt/hostname of the device + print("{:<40}".format(prompt), end="", flush=True) + try: + # Ensure we are in enable mode and can make changes. + if "#" not in prompt[-1]: + net_connect.enable() + print("#", end="", flush=True) + except Exception: + print("Unable to enter enable mode.", end="", flush=True) + continue + + else: + for cmd in config_commands: + # Make requested configuration changes. + try: + if cmd in ("w", "wr"): + output = net_connect.save_config() + print("w", end="", flush=True) + else: + output = net_connect.send_config_set(cmd) + if "Invalid input" in output: + # Unsupported command in this IOS version. + print("Invalid input: ", cmd, end="", flush=True) + print("*", end="", flush=True) + except Exception: + # Command failed! Stop processing further commands. + print("!") + break + net_connect.disconnect() + + +if __name__ == "__main__": + main() diff --git a/examples/use_cases/case10_ssh_proxy/conn_ssh_proxy.py b/examples/use_cases/case10_ssh_proxy/conn_ssh_proxy.py new file mode 100644 index 0000000000000000000000000000000000000000..2f0e74868f9f23ef5e89a4724c2453b5faf0c914 --- /dev/null +++ b/examples/use_cases/case10_ssh_proxy/conn_ssh_proxy.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass + +key_file = "/home/gituser/.ssh/test_rsa" + +cisco1 = { + 'device_type': 'cisco_ios', + 'host': 'cisco1.twb-tech.com', + 'username': 'testuser', + 'use_keys': True, + 'key_file': key_file, + 'ssh_config_file': './ssh_config', +} + +net_connect = Netmiko(**cisco1) +print(net_connect.find_prompt()) +output = net_connect.send_command("show ip arp") +print(output) + diff --git a/examples/use_cases/case10_ssh_proxy/ssh_config b/examples/use_cases/case10_ssh_proxy/ssh_config new file mode 100644 index 0000000000000000000000000000000000000000..2f13ac22ce9587763d7d8a12a3e871a938b6f23c --- /dev/null +++ b/examples/use_cases/case10_ssh_proxy/ssh_config @@ -0,0 +1,7 @@ +host jumphost + IdentityFile ~/.ssh/test_rsa + user gituser + hostname 54.241.72.159 + +host * !jumphost + ProxyCommand ssh jumphost nc %h %p diff --git a/examples/use_cases/case11_logging/conn_with_logging.py b/examples/use_cases/case11_logging/conn_with_logging.py new file mode 100644 index 0000000000000000000000000000000000000000..188c45030f224de6671f1a000b5f7e483de132e4 --- /dev/null +++ b/examples/use_cases/case11_logging/conn_with_logging.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass + +import logging +logging.basicConfig(filename='test.log', level=logging.DEBUG) +logger = logging.getLogger("netmiko") + +cisco1 = { + 'host': 'cisco1.twb-tech.com', + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios', +} + +net_connect = Netmiko(**cisco1) +print(net_connect.find_prompt()) +net_connect.disconnect() diff --git a/examples/use_cases/case11_logging/test.log b/examples/use_cases/case11_logging/test.log new file mode 100644 index 0000000000000000000000000000000000000000..f27e55345c7f6aa15ac65ae251cbe92721c752b4 --- /dev/null +++ b/examples/use_cases/case11_logging/test.log @@ -0,0 +1,64 @@ +DEBUG:paramiko.transport:starting thread (client mode): 0xb63946ec +DEBUG:paramiko.transport:Local version/idstring: SSH-2.0-paramiko_2.4.1 +DEBUG:paramiko.transport:Remote version/idstring: SSH-2.0-Cisco-1.25 +INFO:paramiko.transport:Connected (version 2.0, client Cisco-1.25) +DEBUG:paramiko.transport:kex algos:['diffie-hellman-group-exchange-sha1', 'diffie-hellman-group14-sha1', 'diffie-hellman-group1-sha1'] server key:['ssh-rsa'] client encrypt:['aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-cbc', '3des-cbc', 'aes192-cbc', 'aes256-cbc'] server encrypt:['aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-cbc', '3des-cbc', 'aes192-cbc', 'aes256-cbc'] client mac:['hmac-sha1', 'hmac-sha1-96', 'hmac-md5', 'hmac-md5-96'] server mac:['hmac-sha1', 'hmac-sha1-96', 'hmac-md5', 'hmac-md5-96'] client compress:['none'] server compress:['none'] client lang:[''] server lang:[''] kex follows?False +DEBUG:paramiko.transport:Kex agreed: diffie-hellman-group-exchange-sha1 +DEBUG:paramiko.transport:HostKey agreed: ssh-rsa +DEBUG:paramiko.transport:Cipher agreed: aes128-ctr +DEBUG:paramiko.transport:MAC agreed: hmac-sha1 +DEBUG:paramiko.transport:Compression agreed: none +DEBUG:paramiko.transport:Got server p (2048 bits) +DEBUG:paramiko.transport:kex engine KexGex specified hash_algo <built-in function openssl_sha1> +DEBUG:paramiko.transport:Switch to new keys ... +DEBUG:paramiko.transport:Adding ssh-rsa host key for cisco1.twb-tech.com: b'c77967d9e78b5c6d9acaaa55cc7ad897' +DEBUG:paramiko.transport:userauth is OK +INFO:paramiko.transport:Authentication (password) successful! +DEBUG:paramiko.transport:[chan 0] Max packet in: 32768 bytes +DEBUG:paramiko.transport:[chan 0] Max packet out: 4096 bytes +DEBUG:paramiko.transport:Secsh channel 0 opened. +DEBUG:paramiko.transport:[chan 0] Sesch channel 0 request ok +DEBUG:paramiko.transport:[chan 0] Sesch channel 0 request ok +DEBUG:netmiko:read_channel: +pynet-rtr1# +DEBUG:netmiko:read_channel: +DEBUG:netmiko:read_channel: +DEBUG:netmiko:read_channel: +DEBUG:netmiko:write_channel: b'\n' +DEBUG:netmiko:read_channel: +pynet-rtr1# +DEBUG:netmiko:read_channel: +DEBUG:netmiko:read_channel: +DEBUG:netmiko:In disable_paging +DEBUG:netmiko:Command: terminal length 0 + +DEBUG:netmiko:write_channel: b'terminal length 0\n' +DEBUG:netmiko:Pattern is: pynet\-rtr1 +DEBUG:netmiko:_read_channel_expect read_data: ter +DEBUG:netmiko:_read_channel_expect read_data: minal length 0 +pynet-rtr1# +DEBUG:netmiko:Pattern found: pynet\-rtr1 terminal length 0 +pynet-rtr1# +DEBUG:netmiko:terminal length 0 +pynet-rtr1# +DEBUG:netmiko:Exiting disable_paging +DEBUG:netmiko:write_channel: b'terminal width 511\n' +DEBUG:netmiko:Pattern is: pynet\-rtr1 +DEBUG:netmiko:_read_channel_expect read_data: t +DEBUG:netmiko:_read_channel_expect read_data: erminal width 511 +pynet-rtr1# +DEBUG:netmiko:Pattern found: pynet\-rtr1 terminal width 511 +pynet-rtr1# +DEBUG:netmiko:read_channel: +DEBUG:netmiko:read_channel: +DEBUG:netmiko:write_channel: b'\n' +DEBUG:netmiko:read_channel: +pynet-rtr1# +DEBUG:netmiko:read_channel: +DEBUG:netmiko:write_channel: b'\n' +DEBUG:netmiko:read_channel: +pynet-rtr1# +DEBUG:netmiko:read_channel: +DEBUG:netmiko:read_channel: +DEBUG:netmiko:exit_config_mode: +DEBUG:netmiko:write_channel: b'exit\n' diff --git a/examples/use_cases/case11_logging/write_channel.py b/examples/use_cases/case11_logging/write_channel.py new file mode 100644 index 0000000000000000000000000000000000000000..5b01f718f769872cbfc1ea818c0d6cacb81add6e --- /dev/null +++ b/examples/use_cases/case11_logging/write_channel.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass +import time + +cisco1 = { + 'host': 'cisco1.twb-tech.com', + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios', +} + +net_connect = Netmiko(**cisco1) +print(net_connect.find_prompt()) +net_connect.write_channel("show ip int brief\n") +time.sleep(1) +output = net_connect.read_channel() +print(output) +net_connect.disconnect() diff --git a/examples/use_cases/case12_telnet/conn_telnet.py b/examples/use_cases/case12_telnet/conn_telnet.py new file mode 100644 index 0000000000000000000000000000000000000000..362a6580d7b4385013ea8c1328d2eb4ce054e461 --- /dev/null +++ b/examples/use_cases/case12_telnet/conn_telnet.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass + +cisco1 = { + 'host': 'cisco1.twb-tech.com', + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios_telnet', +} + +net_connect = Netmiko(**cisco1) +print(net_connect.send_command("show ip arp")) +net_connect.disconnect() diff --git a/examples/use_cases/case13_term_server/term_server.py b/examples/use_cases/case13_term_server/term_server.py new file mode 100644 index 0000000000000000000000000000000000000000..1239cab8406038fb127616c796fd55bd0ffcef17 --- /dev/null +++ b/examples/use_cases/case13_term_server/term_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +import time +from netmiko import ConnectHandler, redispatch + +net_connect = ConnectHandler( + device_type='terminal_server', + ip='10.10.10.10', + username='admin', + password='admin123', + secret='secret123') + +# Manually handle interaction in the Terminal Server (fictional example, but +# hopefully you see the pattern) +net_connect.write_channel("\r\n") +time.sleep(1) +net_connect.write_channel("\r\n") +time.sleep(1) +output = net_connect.read_channel() +# Should hopefully see the terminal server prompt +print(output) + +# Login to end device from terminal server +net_connect.write_channel("connect 1\r\n") +time.sleep(1) + +# Manually handle the Username and Password +max_loops = 10 +i = 1 +while i <= max_loops: + output = net_connect.read_channel() + + if 'Username' in output: + net_connect.write_channel(net_connect.username + '\r\n') + time.sleep(1) + output = net_connect.read_channel() + + # Search for password pattern / send password + if 'Password' in output: + net_connect.write_channel(net_connect.password + '\r\n') + time.sleep(.5) + output = net_connect.read_channel() + # Did we successfully login + if '>' in output or '#' in output: + break + + net_connect.write_channel('\r\n') + time.sleep(.5) + i += 1 + +# We are now logged into the end device +# Dynamically reset the class back to the proper Netmiko class +redispatch(net_connect, device_type='cisco_ios') + +# Now just do your normal Netmiko operations +new_output = net_connect.send_command("show ip int brief") diff --git a/examples/use_cases/case14_secure_copy/secure_copy.py b/examples/use_cases/case14_secure_copy/secure_copy.py new file mode 100644 index 0000000000000000000000000000000000000000..bd67a1594fa2cff92a55adfee5d8b26615f5624d --- /dev/null +++ b/examples/use_cases/case14_secure_copy/secure_copy.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +from getpass import getpass +from netmiko import ConnectHandler, file_transfer + +password = getpass() + +cisco = { + 'device_type': 'cisco_ios', + 'host': 'cisco1.twb-tech.com', + 'username': 'pyclass', + 'password': password, +} + +source_file = 'test1.txt' +dest_file = 'test1.txt' +direction = 'put' +file_system = 'flash:' + +ssh_conn = ConnectHandler(**cisco) +transfer_dict = file_transfer(ssh_conn, + source_file=source_file, + dest_file=dest_file, + file_system=file_system, + direction=direction, + overwrite_file=True) + +print(transfer_dict) diff --git a/examples/use_cases/case14_secure_copy/test1.txt b/examples/use_cases/case14_secure_copy/test1.txt new file mode 100644 index 0000000000000000000000000000000000000000..982793c32ee7f2df6107a721a7814fcf5ec39bbd --- /dev/null +++ b/examples/use_cases/case14_secure_copy/test1.txt @@ -0,0 +1 @@ +whatever diff --git a/examples/use_cases/case15_netmiko_tools/.netmiko.yml_example b/examples/use_cases/case15_netmiko_tools/.netmiko.yml_example new file mode 100644 index 0000000000000000000000000000000000000000..147fe98a9a314daa7364f1b6870ec0757a360c7d --- /dev/null +++ b/examples/use_cases/case15_netmiko_tools/.netmiko.yml_example @@ -0,0 +1,69 @@ +--- + +# Dictionary is a device +pynet_rtr1: + device_type: cisco_ios + host: cisco1.twb-tech.com + username: admin + password: cisco123 + port: 22 + +pynet_rtr2: + device_type: cisco_ios + ip: 10.10.10.71 + username: admin + password: cisco123 + +arista_sw1: + device_type: arista_eos + ip: 10.10.10.72 + username: admin + password: cisco123 + +arista_sw2: + device_type: arista_eos + ip: 10.10.10.73 + username: admin + password: cisco123 + +arista_sw3: + device_type: arista_eos + ip: 10.10.10.74 + username: admin + password: cisco123 + +arista_sw4: + device_type: arista_eos + ip: 10.10.10.75 + username: admin + password: cisco123 + +juniper_srx: + device_type: juniper + ip: 10.10.10.76 + username: admin + password: cisco123 + +cisco_asa: + device_type: cisco_asa + ip: 10.10.10.1 + username: admin + password: cisco123 + secret: secret + +# Any list is group of devices +cisco: + - pynet_rtr1 + - pynet_rtr2 + +asa: + - cisco_asa + +arista: + - arista_sw1 + - arista_sw2 + - arista_sw3 + - arista_sw4 + +juniper: + - juniper_srx diff --git a/examples/use_cases/case15_netmiko_tools/vlans.txt b/examples/use_cases/case15_netmiko_tools/vlans.txt new file mode 100644 index 0000000000000000000000000000000000000000..b60cdf4bd13e91ea4d9eacafadd9f1a9df55ce7d --- /dev/null +++ b/examples/use_cases/case15_netmiko_tools/vlans.txt @@ -0,0 +1,6 @@ +vlan 100 + name red100 +vlan 101 + name red101 +vlan 102 + name red102 diff --git a/examples/use_cases/case16_concurrency/my_devices.py b/examples/use_cases/case16_concurrency/my_devices.py new file mode 100644 index 0000000000000000000000000000000000000000..6f3fb673a538f9547e0d0ec360040eec9f933143 --- /dev/null +++ b/examples/use_cases/case16_concurrency/my_devices.py @@ -0,0 +1,62 @@ +from getpass import getpass + +std_pwd = getpass("Enter standard password: ") + +pynet_rtr1 = { + 'device_type': 'cisco_ios', + 'ip': '10.10.247.70', + 'username': 'pyclass', + 'password': std_pwd, +} + +pynet_rtr2 = { + 'device_type': 'cisco_ios', + 'ip': '10.10.247.71', + 'username': 'pyclass', + 'password': std_pwd, +} + +pynet_sw1 = { + 'device_type': 'arista_eos', + 'ip': '10.10.247.72', + 'username': 'pyclass', + 'password': std_pwd, +} + +pynet_sw2 = { + 'device_type': 'arista_eos', + 'ip': '10.10.247.73', + 'username': 'pyclass', + 'password': std_pwd, +} + +pynet_sw3 = { + 'device_type': 'arista_eos', + 'ip': '10.10.247.74', + 'username': 'pyclass', + 'password': std_pwd, +} + +pynet_sw4 = { + 'device_type': 'arista_eos', + 'ip': '10.10.247.75', + 'username': 'pyclass', + 'password': std_pwd, +} + +juniper_srx = { + 'device_type': 'juniper_junos', + 'ip': '10.10.247.76', + 'username': 'pyclass', + 'password': std_pwd, +} + +device_list = [ + pynet_rtr1, + pynet_rtr2, + pynet_sw1, + pynet_sw2, + pynet_sw3, + pynet_sw4, + juniper_srx, +] diff --git a/examples/use_cases/case16_concurrency/processes_netmiko.py b/examples/use_cases/case16_concurrency/processes_netmiko.py new file mode 100644 index 0000000000000000000000000000000000000000..7e0761dfedf8cf5dd243c24b0e2240d71dd1432c --- /dev/null +++ b/examples/use_cases/case16_concurrency/processes_netmiko.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +''' +Use processes and Netmiko to connect to each of the devices. Execute +'show version' on each device. Record the amount of time required to do this. +''' +from __future__ import print_function, unicode_literals +from multiprocessing import Process + +from datetime import datetime +from netmiko import ConnectHandler +from my_devices import device_list as devices + + +def show_version(a_device): + '''Execute show version command using Netmiko.''' + remote_conn = ConnectHandler(**a_device) + print() + print('#' * 80) + print(remote_conn.send_command("show version")) + print('#' * 80) + print() + + +def main(): + ''' + Use processes and Netmiko to connect to each of the devices. Execute + 'show version' on each device. Record the amount of time required to do this. + ''' + start_time = datetime.now() + + procs = [] + for a_device in devices: + my_proc = Process(target=show_version, args=(a_device,)) + my_proc.start() + procs.append(my_proc) + + for a_proc in procs: + print(a_proc) + a_proc.join() + + print("\nElapsed time: " + str(datetime.now() - start_time)) + + +if __name__ == "__main__": + main() diff --git a/examples/use_cases/case16_concurrency/processes_netmiko_queue.py b/examples/use_cases/case16_concurrency/processes_netmiko_queue.py new file mode 100644 index 0000000000000000000000000000000000000000..00dd8e8ab57d2269370ab711713ac9215788a71e --- /dev/null +++ b/examples/use_cases/case16_concurrency/processes_netmiko_queue.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +''' +Use processes and Netmiko to connect to each of the devices. Execute +'show version' on each device. Use a queue to pass the output back to the parent process. +Record the amount of time required to do this. +''' +from __future__ import print_function, unicode_literals +from multiprocessing import Process, Queue + +from datetime import datetime +from netmiko import ConnectHandler +from my_devices import device_list as devices + + +def show_version_queue(a_device, output_q): + ''' + Use Netmiko to execute show version. Use a queue to pass the data back to + the main process. + ''' + output_dict = {} + remote_conn = ConnectHandler(**a_device) + hostname = remote_conn.base_prompt + output = ('#' * 80) + "\n" + output += remote_conn.send_command("show version") + "\n" + output += ('#' * 80) + "\n" + output_dict[hostname] = output + output_q.put(output_dict) + + +def main(): + ''' + Use processes and Netmiko to connect to each of the devices. Execute + 'show version' on each device. Use a queue to pass the output back to the parent process. + Record the amount of time required to do this. + ''' + start_time = datetime.now() + output_q = Queue(maxsize=20) + + procs = [] + for a_device in devices: + my_proc = Process(target=show_version_queue, args=(a_device, output_q)) + my_proc.start() + procs.append(my_proc) + + # Make sure all processes have finished + for a_proc in procs: + a_proc.join() + + while not output_q.empty(): + my_dict = output_q.get() + for k, val in my_dict.items(): + print(k) + print(val) + + print("\nElapsed time: " + str(datetime.now() - start_time)) + + +if __name__ == "__main__": + main() diff --git a/examples/use_cases/case16_concurrency/threads_netmiko.py b/examples/use_cases/case16_concurrency/threads_netmiko.py new file mode 100644 index 0000000000000000000000000000000000000000..6fcac1bd933c801e51c55c422e11845c972f836b --- /dev/null +++ b/examples/use_cases/case16_concurrency/threads_netmiko.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +import threading +from datetime import datetime +from netmiko import ConnectHandler +from my_devices import device_list as devices + + +def show_version(a_device): + '''Execute show version command using Netmiko.''' + remote_conn = ConnectHandler(**a_device) + print() + print('#' * 80) + print(remote_conn.send_command_expect("show version")) + print('#' * 80) + print() + + +def main(): + ''' + Use threads and Netmiko to connect to each of the devices. Execute + 'show version' on each device. Record the amount of time required to do this. + ''' + start_time = datetime.now() + + for a_device in devices: + my_thread = threading.Thread(target=show_version, args=(a_device,)) + my_thread.start() + + main_thread = threading.currentThread() + for some_thread in threading.enumerate(): + if some_thread != main_thread: + print(some_thread) + some_thread.join() + + print("\nElapsed time: " + str(datetime.now() - start_time)) + + +if __name__ == "__main__": + main() diff --git a/examples/use_cases/case17_jinja2/jinja2_crypto.py b/examples/use_cases/case17_jinja2/jinja2_crypto.py new file mode 100755 index 0000000000000000000000000000000000000000..dad05793e7b05eb43258150ae8b19b1670778159 --- /dev/null +++ b/examples/use_cases/case17_jinja2/jinja2_crypto.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +from __future__ import print_function, unicode_literals +import jinja2 + +template_vars = { + 'isakmp_enable': True, + 'encryption': 'aes', + 'dh_group': 5, +} + +cfg_template = ''' + +{%- if isakmp_enable %} +crypto isakmp policy 10 + encr {{ encryption }} + authentication pre-share + group {{ dh_group }} +crypto isakmp key my_key address 1.1.1.1 no-xauth +crypto isakmp keepalive 10 periodic +{%- endif %} + +''' + +template = jinja2.Template(cfg_template) +print(template.render(template_vars)) diff --git a/examples/use_cases/case17_jinja2/jinja2_for_loop.py b/examples/use_cases/case17_jinja2/jinja2_for_loop.py new file mode 100755 index 0000000000000000000000000000000000000000..caf7d50cfe215e8cd929d3ea222c4eff617c8cfd --- /dev/null +++ b/examples/use_cases/case17_jinja2/jinja2_for_loop.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +from __future__ import print_function, unicode_literals +import jinja2 + +my_vlans = { + '501': 'blue501', + '502': 'blue502', + '503': 'blue503', + '504': 'blue504', + '505': 'blue505', + '506': 'blue506', + '507': 'blue507', + '508': 'blue508', +} +template_vars = { + 'vlans': my_vlans +} + +vlan_template = ''' + +{%- for vlan_id, vlan_name in vlans.items() %} +vlan {{ vlan_id }} + name {{ vlan_name }} +{%- endfor %} + +''' + +template = jinja2.Template(vlan_template) +print(template.render(template_vars)) diff --git a/examples/use_cases/case17_jinja2/jinja2_ospf_file.py b/examples/use_cases/case17_jinja2/jinja2_ospf_file.py new file mode 100755 index 0000000000000000000000000000000000000000..a3211a365695b7447956f9823de6fe8c2054b172 --- /dev/null +++ b/examples/use_cases/case17_jinja2/jinja2_ospf_file.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +from __future__ import print_function, unicode_literals +import jinja2 + +template_file = 'ospf_config.j2' +with open(template_file) as f: + jinja_template = f.read() + +ospf_active_interfaces = ['Vlan1', 'Vlan2'] +area0_networks = [ + '10.10.10.0/24', + '10.10.20.0/24', + '10.10.30.0/24', +] +template_vars = { + 'ospf_process_id': 10, + 'ospf_priority': 100, + 'ospf_active_interfaces': ospf_active_interfaces, + 'ospf_area0_networks': area0_networks, +} + +template = jinja2.Template(jinja_template) +print(template.render(template_vars)) diff --git a/examples/use_cases/case17_jinja2/jinja2_vlans.py b/examples/use_cases/case17_jinja2/jinja2_vlans.py new file mode 100755 index 0000000000000000000000000000000000000000..58f6842afa84a5d13d77eab4e51afa87a50de7c5 --- /dev/null +++ b/examples/use_cases/case17_jinja2/jinja2_vlans.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +from __future__ import print_function, unicode_literals +import jinja2 + +template_vars = { + 'vlan_id': 400, + 'vlan_name': 'red400', +} + +vlan_template = ''' +vlan {{ vlan_id }} + name {{ vlan_name }} + +''' + +template = jinja2.Template(vlan_template) +print(template.render(template_vars)) diff --git a/examples/use_cases/case17_jinja2/ospf_config.j2 b/examples/use_cases/case17_jinja2/ospf_config.j2 new file mode 100644 index 0000000000000000000000000000000000000000..10f4043a70f9842ca84096841ce1cd7bd43c367c --- /dev/null +++ b/examples/use_cases/case17_jinja2/ospf_config.j2 @@ -0,0 +1,16 @@ + +{%- if ospf_priority is defined %} +interface Vlan1 + ip ospf priority {{ ospf_priority }} +{%- endif %} + +router ospf {{ ospf_process_id }} + passive-interface default + {%- for intf in ospf_active_interfaces %} + no passive-interface {{ intf }} + {%- endfor %} + {%- for network in ospf_area0_networks %} + network {{ network }} area 0.0.0.0 + {%- endfor %} + max-lsa 12000 + diff --git a/examples/use_cases/case1_simple_conn/simple_conn.py b/examples/use_cases/case1_simple_conn/simple_conn.py new file mode 100644 index 0000000000000000000000000000000000000000..50772791c5bfe1cee8ca7baa72b9aab1580d7114 --- /dev/null +++ b/examples/use_cases/case1_simple_conn/simple_conn.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass + +net_connect = Netmiko('cisco1.twb-tech.com', username='pyclass', + password=getpass(), device_type='cisco_ios') + +print(net_connect.find_prompt()) +net_connect.disconnect() diff --git a/examples/use_cases/case2_using_dict/conn_with_dict.py b/examples/use_cases/case2_using_dict/conn_with_dict.py new file mode 100644 index 0000000000000000000000000000000000000000..1ad926055f704d7d18c8e6cec453ae1852059d18 --- /dev/null +++ b/examples/use_cases/case2_using_dict/conn_with_dict.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass + +cisco1 = { + 'host': 'cisco1.twb-tech.com', + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios', +} + +net_connect = Netmiko(**cisco1) +print(net_connect.find_prompt()) +net_connect.disconnect() diff --git a/examples/use_cases/case2_using_dict/enable.py b/examples/use_cases/case2_using_dict/enable.py new file mode 100644 index 0000000000000000000000000000000000000000..70056c8c1a1a88bfa3abe496a8efbacc2bf4c963 --- /dev/null +++ b/examples/use_cases/case2_using_dict/enable.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass + +password = getpass() + +cisco1 = { + 'host': 'cisco1.twb-tech.com', + 'username': 'pyclass', + 'password': password, + 'device_type': 'cisco_ios', + 'secret': password, +} + +net_connect = Netmiko(**cisco1) +print(net_connect.find_prompt()) +net_connect.send_command_timing("disable") +print(net_connect.find_prompt()) +net_connect.enable() +print(net_connect.find_prompt()) + +# Go into config mode +net_connect.config_mode() +print(net_connect.find_prompt()) +net_connect.exit_config_mode() +print(net_connect.find_prompt()) +net_connect.disconnect() diff --git a/examples/use_cases/case2_using_dict/invalid_device_type.py b/examples/use_cases/case2_using_dict/invalid_device_type.py new file mode 100644 index 0000000000000000000000000000000000000000..73135213a92c885fbb21738d8a3f20da6d618335 --- /dev/null +++ b/examples/use_cases/case2_using_dict/invalid_device_type.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass + +cisco1 = { + 'host': 'cisco1.twb-tech.com', + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'whatever', +} + +net_connect = Netmiko(**cisco1) +print(net_connect.find_prompt()) +net_connect.disconnect() diff --git a/examples/use_cases/case3_multiple_devices/conn_multiple_dev.py b/examples/use_cases/case3_multiple_devices/conn_multiple_dev.py new file mode 100644 index 0000000000000000000000000000000000000000..3ef9bd9a61ee853343d397751c2f54752a722b0b --- /dev/null +++ b/examples/use_cases/case3_multiple_devices/conn_multiple_dev.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass + +password = getpass() + +cisco1 = { + 'host': 'cisco1.twb-tech.com', + 'username': 'pyclass', + 'password': password, + 'device_type': 'cisco_ios', +} + +cisco2 = { + 'host': 'cisco2.twb-tech.com', + 'username': 'pyclass', + 'password': password, + 'device_type': 'cisco_ios', +} + +nxos1 = { + 'host': 'nxos1.twb-tech.com', + 'username': 'pyclass', + 'password': password, + 'device_type': 'cisco_nxos', +} + +srx1 = { + 'host': 'srx1.twb-tech.com', + 'username': 'pyclass', + 'password': password, + 'device_type': 'juniper_junos', +} + +for device in (cisco1, cisco2, nxos1, srx1): + net_connect = Netmiko(**device) + print(net_connect.find_prompt()) diff --git a/examples/use_cases/case4_show_commands/send_command.py b/examples/use_cases/case4_show_commands/send_command.py new file mode 100644 index 0000000000000000000000000000000000000000..1294d6717a6fd3e736b10fcd26c8bdeaee175c4a --- /dev/null +++ b/examples/use_cases/case4_show_commands/send_command.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass + +cisco1 = { + 'host': 'cisco1.twb-tech.com', + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios', +} + +net_connect = Netmiko(**cisco1) +command = 'show ip int brief' + +print() +print(net_connect.find_prompt()) +output = net_connect.send_command(command) +net_connect.disconnect() +print(output) +print() diff --git a/examples/use_cases/case4_show_commands/send_command_delay.py b/examples/use_cases/case4_show_commands/send_command_delay.py new file mode 100644 index 0000000000000000000000000000000000000000..d223a59a72ecd15a71c6fa2006d5f2810a5b1360 --- /dev/null +++ b/examples/use_cases/case4_show_commands/send_command_delay.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass + +cisco1 = { + 'host': 'cisco1.twb-tech.com', + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios', +} + +net_connect = Netmiko(**cisco1) +command = 'copy flash:c880data-universalk9-mz.154-2.T1.bin flash:test1.bin' + +print() +print(net_connect.find_prompt()) +output = net_connect.send_command(command, delay_factor=4) +print(output) +print() diff --git a/examples/use_cases/case4_show_commands/send_command_expect.py b/examples/use_cases/case4_show_commands/send_command_expect.py new file mode 100644 index 0000000000000000000000000000000000000000..3c2441aee37a608a835756b44a0020af39f4fbf4 --- /dev/null +++ b/examples/use_cases/case4_show_commands/send_command_expect.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass + +cisco1 = { + 'host': 'cisco1.twb-tech.com', + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios', +} + +net_connect = Netmiko(**cisco1) +command = 'show ip int brief' + +print() +print(net_connect.find_prompt()) +output = net_connect.send_command(command, expect_string=r'#') +net_connect.disconnect() +print(output) +print() diff --git a/examples/use_cases/case4_show_commands/send_command_textfsm.py b/examples/use_cases/case4_show_commands/send_command_textfsm.py new file mode 100644 index 0000000000000000000000000000000000000000..517705a7fc700c1ae4013ca29545dab48bdcb07e --- /dev/null +++ b/examples/use_cases/case4_show_commands/send_command_textfsm.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass + +cisco1 = { + 'host': 'cisco1.twb-tech.com', + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios', +} + +net_connect = Netmiko(**cisco1) +command = 'show ip int brief' + +print() +print(net_connect.find_prompt()) +output = net_connect.send_command(command, use_textfsm=True) +net_connect.disconnect() +print(output) +print() diff --git a/examples/use_cases/case5_prompting/send_command_prompting.py b/examples/use_cases/case5_prompting/send_command_prompting.py new file mode 100644 index 0000000000000000000000000000000000000000..e9cee9b0d29975f013e2abb2372a6e9069258bd7 --- /dev/null +++ b/examples/use_cases/case5_prompting/send_command_prompting.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass + +cisco1 = { + 'host': 'cisco1.twb-tech.com', + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_ios', +} + +net_connect = Netmiko(**cisco1) +command = 'del flash:/test1.txt' +print() +print(net_connect.find_prompt()) +output = net_connect.send_command_timing(command) +if 'confirm' in output: + output += net_connect.send_command_timing('y', strip_prompt=False, strip_command=False) +net_connect.disconnect() +print(output) +print() diff --git a/examples/use_cases/case6_config_change/config_change.py b/examples/use_cases/case6_config_change/config_change.py new file mode 100644 index 0000000000000000000000000000000000000000..8afa9ca52ce0c467819f25bb66b43467dddb6d3d --- /dev/null +++ b/examples/use_cases/case6_config_change/config_change.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass + +nxos1 = { + 'host': 'nxos1.twb-tech.com', + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_nxos', +} + +commands = [ + 'logging history size 500' +] + +net_connect = Netmiko(**nxos1) + +print() +print(net_connect.find_prompt()) +output = net_connect.send_config_set(commands) +output += net_connect.send_command("copy run start") +print(output) +print() diff --git a/examples/use_cases/case6_config_change/config_change_file.py b/examples/use_cases/case6_config_change/config_change_file.py new file mode 100644 index 0000000000000000000000000000000000000000..e2bde411f101e0040f904548e84afc779cc98dce --- /dev/null +++ b/examples/use_cases/case6_config_change/config_change_file.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass + +nxos1 = { + 'host': 'nxos1.twb-tech.com', + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'cisco_nxos', +} + +cfg_file = 'config_changes.txt' +net_connect = Netmiko(**nxos1) + +print() +print(net_connect.find_prompt()) +output = net_connect.send_config_from_file(cfg_file) +print(output) +print() + +net_connect.save_config() +net_connect.disconnect() diff --git a/examples/use_cases/case6_config_change/config_changes.txt b/examples/use_cases/case6_config_change/config_changes.txt new file mode 100644 index 0000000000000000000000000000000000000000..35d43ba8940749b1eb2434710af5f9ca634b65f8 --- /dev/null +++ b/examples/use_cases/case6_config_change/config_changes.txt @@ -0,0 +1,6 @@ +vlan 100 + name blue100 +vlan 101 + name blue101 +vlan 102 + name blue102 diff --git a/examples/use_cases/case7_commit/config_change_jnpr.py b/examples/use_cases/case7_commit/config_change_jnpr.py new file mode 100644 index 0000000000000000000000000000000000000000..1fccaa1883654743284edd95b55416dda4f2a687 --- /dev/null +++ b/examples/use_cases/case7_commit/config_change_jnpr.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass + +device = { + 'host': 'srx1.twb-tech.com', + 'username': 'pyclass', + 'password': getpass(), + 'device_type': 'juniper_junos', +} + +commands = [ + 'set system syslog archive size 240k files 3 ' +] + +net_connect = Netmiko(**device) + +print() +print(net_connect.find_prompt()) +output = net_connect.send_config_set(commands, exit_config_mode=False) +output += net_connect.commit(and_quit=True) +print(output) +print() + +net_connect.disconnect() diff --git a/examples/use_cases/case8_autodetect/autodetect_snmp.py b/examples/use_cases/case8_autodetect/autodetect_snmp.py new file mode 100644 index 0000000000000000000000000000000000000000..0c60eca15c1c913913e808ef691f3cd7adb1b78a --- /dev/null +++ b/examples/use_cases/case8_autodetect/autodetect_snmp.py @@ -0,0 +1,18 @@ +from netmiko.snmp_autodetect import SNMPDetect +from netmiko import Netmiko +from getpass import getpass + +device = { + 'host': 'cisco1.twb-tech.com', + 'username': 'pyclass', + 'password': getpass(), +} + +snmp_community = getpass("Enter SNMP community: ") +my_snmp = SNMPDetect("cisco1.twb-tech.com", snmp_version="v2c", community=snmp_community) +device_type = my_snmp.autodetect() +print(device_type) + +device['device_type'] = device_type +net_connect = Netmiko(**device) +print(net_connect.find_prompt()) diff --git a/examples/use_cases/case8_autodetect/autodetect_snmp_v3.py b/examples/use_cases/case8_autodetect/autodetect_snmp_v3.py new file mode 100644 index 0000000000000000000000000000000000000000..65b726d1a8c714ac952ba28af1f8853ec4bb1f92 --- /dev/null +++ b/examples/use_cases/case8_autodetect/autodetect_snmp_v3.py @@ -0,0 +1,21 @@ +# SNMPv3 +from netmiko.snmp_autodetect import SNMPDetect +from netmiko import Netmiko +from getpass import getpass + +device = { + 'host': 'cisco1.twb-tech.com', + 'username': 'pyclass', + 'password': getpass(), +} + +snmp_key = getpass("Enter SNMP community: ") +my_snmp = SNMPDetect("cisco1.twb-tech.com", snmp_version="v3", user='pysnmp', + auth_key=snmp_key, encrypt_key=snmp_key, auth_proto="sha", + encrypt_proto="aes128") +device_type = my_snmp.autodetect() +print(device_type) + +device['device_type'] = device_type +net_connect = Netmiko(**device) +print(net_connect.find_prompt()) diff --git a/examples/use_cases/case8_autodetect/autodetect_ssh.py b/examples/use_cases/case8_autodetect/autodetect_ssh.py new file mode 100644 index 0000000000000000000000000000000000000000..0f95c3323bb02ddc5dec2854795f27d8d888dd26 --- /dev/null +++ b/examples/use_cases/case8_autodetect/autodetect_ssh.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +from netmiko import SSHDetect, Netmiko +from getpass import getpass + +device = { + 'device_type': 'autodetect', + 'host': 'cisco1.twb-tech.com', + 'username': 'pyclass', + 'password': getpass(), +} + +guesser = SSHDetect(**device) +best_match = guesser.autodetect() +print(best_match) # Name of the best device_type to use further +print(guesser.potential_matches) # Dictionary of the whole matching result + +device['device_type'] = best_match +connection = Netmiko(**device) + +print(connection.find_prompt()) diff --git a/examples/use_cases/case9_ssh_keys/conn_ssh_keys.py b/examples/use_cases/case9_ssh_keys/conn_ssh_keys.py new file mode 100644 index 0000000000000000000000000000000000000000..a1c17bed07ff460df56e5c8191ea5b42ccf1e6fa --- /dev/null +++ b/examples/use_cases/case9_ssh_keys/conn_ssh_keys.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +from netmiko import Netmiko +from getpass import getpass + +key_file = "/home/gituser/.ssh/test_rsa" + +cisco1 = { + 'device_type': 'cisco_ios', + 'host': 'cisco1.twb-tech.com', + 'username': 'testuser', + 'use_keys': True, + 'key_file': key_file, +} + +net_connect = Netmiko(**cisco1) +print(net_connect.find_prompt()) +output = net_connect.send_command("show ip arp") +print(output) diff --git a/netmiko/__init__.py b/netmiko/__init__.py index f2f600d9cb7dddf6762469dd5d5eb55a8d1b491e..e5e3c69d2d7b8d93765ac8cb067d7b3c144f5111 100644 --- a/netmiko/__init__.py +++ b/netmiko/__init__.py @@ -23,7 +23,7 @@ NetmikoTimeoutError = NetMikoTimeoutException NetmikoAuthError = NetMikoAuthenticationException Netmiko = ConnectHandler -__version__ = '2.1.1' +__version__ = '2.2.0' __all__ = ('ConnectHandler', 'ssh_dispatcher', 'platforms', 'SCPConn', 'FileTransfer', 'NetMikoTimeoutException', 'NetMikoAuthenticationException', 'NetmikoTimeoutError', 'NetmikoAuthError', 'InLineTransfer', 'redispatch', diff --git a/netmiko/apresia/__init__.py b/netmiko/apresia/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e15e8702a22b18b8293b2076b87b14602aa9e46c --- /dev/null +++ b/netmiko/apresia/__init__.py @@ -0,0 +1,4 @@ +from __future__ import unicode_literals +from netmiko.apresia.apresia_aeos import ApresiaAeosSSH, ApresiaAeosTelnet + +__all__ = ['ApresiaAeosSSH', 'ApresiaAeosTelnet'] diff --git a/netmiko/apresia/apresia_aeos.py b/netmiko/apresia/apresia_aeos.py new file mode 100644 index 0000000000000000000000000000000000000000..e310218ad6a45b9b779e32dd278506c70f3180e0 --- /dev/null +++ b/netmiko/apresia/apresia_aeos.py @@ -0,0 +1,39 @@ +from __future__ import unicode_literals +import time +from netmiko.cisco_base_connection import CiscoSSHConnection + + +class ApresiaAeosBase(CiscoSSHConnection): + def session_preparation(self): + """Prepare the session after the connection has been established.""" + self._test_channel_read(pattern=r'[>#]') + self.set_base_prompt() + self.disable_paging() + self.set_terminal_width(command='terminal width 511') + # Clear the read buffer + time.sleep(.3 * self.global_delay_factor) + self.clear_buffer() + + def disable_paging(self, command="", delay_factor=1): + self.enable() + check_command = "show running-config | include terminal length 0" + output = self.send_command(check_command) + + if "terminal length 0" not in output: + self.send_config_set("terminal length 0") + self.exit_enable_mode() + + def set_terminal_width(self, command="", delay_factor=1): + """No terminal width command mode on AEOS""" + pass + + +class ApresiaAeosSSH(ApresiaAeosBase): + pass + + +class ApresiaAeosTelnet(ApresiaAeosBase): + def __init__(self, *args, **kwargs): + default_enter = kwargs.get('default_enter') + kwargs['default_enter'] = '\r\n' if default_enter is None else default_enter + super(ApresiaAeosTelnet, self).__init__(*args, **kwargs) diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index baf4da046f8af1218610515595556c5ec69728f1..975e7ada3679523ff00cef7205a0221583d22827 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -10,21 +10,22 @@ Also defines methods that should generally be supported by child classes from __future__ import print_function from __future__ import unicode_literals -import paramiko +import io +import re +import socket import telnetlib import time -import socket -import re -import io from os import path from threading import Lock +import paramiko +import serial + +from netmiko import log from netmiko.netmiko_globals import MAX_BUFFER, BACKSPACE_CHAR +from netmiko.py23_compat import string_types, bufferedio_types from netmiko.ssh_exception import NetMikoTimeoutException, NetMikoAuthenticationException from netmiko.utilities import write_bytes, check_serial_port, get_structured_data -from netmiko.py23_compat import string_types -from netmiko import log -import serial class BaseConnection(object): @@ -36,9 +37,10 @@ class BaseConnection(object): def __init__(self, ip='', host='', username='', password='', secret='', port=None, device_type='', verbose=False, global_delay_factor=1, use_keys=False, key_file=None, allow_agent=False, ssh_strict=False, system_host_keys=False, - alt_host_keys=False, alt_key_file='', ssh_config_file=None, timeout=90, + alt_host_keys=False, alt_key_file='', ssh_config_file=None, timeout=100, session_timeout=60, blocking_timeout=8, keepalive=0, default_enter=None, - response_return=None, serial_settings=None): + response_return=None, serial_settings=None, fast_cli=False, session_log=None, + session_log_record_writes=False, session_log_file_mode='write'): """ Initialize attributes for establishing connection to target device. @@ -116,6 +118,23 @@ class BaseConnection(object): :param response_return: Character(s) to use in normalized return data to represent enter key (default: '\n') :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 """ self.remote_conn = None self.RETURN = '\n' if default_enter is None else default_enter @@ -147,6 +166,23 @@ class BaseConnection(object): self.blocking_timeout = blocking_timeout self.keepalive = keepalive + # Netmiko will close the session_log if we open the file + self.session_log = None + self.session_log_record_writes = session_log_record_writes + self._session_log_close = False + # Ensures last write operations prior to disconnect are recorded. + self._session_log_fin = False + if session_log is not None: + if isinstance(session_log, string_types): + # If session_log is a string, open a file corresponding to string name. + self.open_session_log(filename=session_log, mode=session_log_file_mode) + elif isinstance(session_log, bufferedio_types): + # In-memory buffer or an already open file handle + self.session_log = session_log + else: + raise ValueError("session_log must be a path to a file, a file handle, " + "or a BufferedIOBase subclass.") + # Default values self.serial_settings = { 'port': 'COM1', @@ -166,8 +202,10 @@ class BaseConnection(object): comm_port = check_serial_port(comm_port) self.serial_settings.update({'port': comm_port}) - # Use the greater of global_delay_factor or delay_factor local to method + self.fast_cli = fast_cli self.global_delay_factor = global_delay_factor + if self.fast_cli and self.global_delay_factor == 1: + self.global_delay_factor = .1 # set in set_base_prompt method self.base_prompt = '' @@ -275,10 +313,17 @@ class BaseConnection(object): raise ValueError("Invalid protocol specified") try: log.debug("write_channel: {}".format(write_bytes(out_data))) + if self._session_log_fin or self.session_log_record_writes: + self._write_session_log(out_data) except UnicodeDecodeError: # Don't log non-ASCII characters; this is null characters and telnet IAC (PY2) pass + def _write_session_log(self, data): + if self.session_log is not None and len(data) > 0: + self.session_log.write(write_bytes(data)) + self.session_log.flush() + def write_channel(self, out_data): """Generic handler that will write to both SSH and telnet channel. @@ -339,6 +384,7 @@ class BaseConnection(object): while (self.remote_conn.in_waiting > 0): output += self.remote_conn.read(self.remote_conn.in_waiting) log.debug("read_channel: {}".format(output)) + self._write_session_log(output) return output def read_channel(self): @@ -399,6 +445,7 @@ class BaseConnection(object): new_data = new_data.decode('utf-8', 'ignore') log.debug("_read_channel_expect read_data: {}".format(new_data)) output += new_data + self._write_session_log(new_data) except socket.timeout: raise NetMikoTimeoutException("Timed-out reading channel, data not available.") finally: @@ -491,7 +538,7 @@ class BaseConnection(object): pwd_pattern, delay_factor, max_loops) def telnet_login(self, pri_prompt_terminator=r'#\s*$', alt_prompt_terminator=r'>\s*$', - username_pattern=r"(?:[Uu]ser:|sername|ogin)", pwd_pattern=r"assword", + username_pattern=r"(?:user:|username|login|user name)", pwd_pattern=r"assword", delay_factor=1, max_loops=20): """Telnet login. Can be username/password or just password. @@ -522,14 +569,14 @@ class BaseConnection(object): return_msg += output # Search for username pattern / send username - if re.search(username_pattern, output): + if re.search(username_pattern, output, flags=re.I): self.write_channel(self.username + self.TELNET_RETURN) time.sleep(1 * delay_factor) output = self.read_channel() return_msg += output # Search for password pattern / send password - if re.search(pwd_pattern, output): + if re.search(pwd_pattern, output, flags=re.I): self.write_channel(self.password + self.TELNET_RETURN) time.sleep(.5 * delay_factor) output = self.read_channel() @@ -547,7 +594,8 @@ class BaseConnection(object): time.sleep(.5 * delay_factor) i += 1 except EOFError: - msg = "Telnet login failed: {}".format(self.host) + self.remote_conn.close() + msg = "Login failed: {}".format(self.host) raise NetMikoAuthenticationException(msg) # Last try to see if we already logged in @@ -559,7 +607,8 @@ class BaseConnection(object): or re.search(alt_prompt_terminator, output, flags=re.M)): return return_msg - msg = "Telnet login failed: {}".format(self.host) + msg = "Login failed: {}".format(self.host) + self.remote_conn.close() raise NetMikoAuthenticationException(msg) def session_preparation(self): @@ -688,17 +737,19 @@ class BaseConnection(object): try: self.remote_conn_pre.connect(**ssh_connect_params) except socket.error: + self.paramiko_cleanup() msg = "Connection to device timed-out: {device_type} {ip}:{port}".format( device_type=self.device_type, ip=self.host, port=self.port) raise NetMikoTimeoutException(msg) except paramiko.ssh_exception.AuthenticationException as auth_err: + self.paramiko_cleanup() msg = "Authentication failure: unable to connect {device_type} {ip}:{port}".format( device_type=self.device_type, ip=self.host, port=self.port) msg += self.RETURN + str(auth_err) raise NetMikoAuthenticationException(msg) if self.verbose: - print("SSH connection established to {0}:{1}".format(self.host, self.port)) + print("SSH connection established to {}:{}".format(self.host, self.port)) # Use invoke_shell to establish an 'interactive session' if width and height: @@ -771,15 +822,23 @@ class BaseConnection(object): return remote_conn_pre def select_delay_factor(self, delay_factor): - """Choose the greater of delay_factor or self.global_delay_factor. + """ + Choose the greater of delay_factor or self.global_delay_factor (default). + In fast_cli choose the lesser of delay_factor of self.global_delay_factor. :param delay_factor: See __init__: global_delay_factor :type delay_factor: int """ - if delay_factor >= self.global_delay_factor: - return delay_factor + if self.fast_cli: + if delay_factor <= self.global_delay_factor: + return delay_factor + else: + return self.global_delay_factor else: - return self.global_delay_factor + if delay_factor >= self.global_delay_factor: + return delay_factor + else: + return self.global_delay_factor def special_login_handler(self, delay_factor=1): """Handler for devices like WLC, Avaya ERS that throw up characters prior to login.""" @@ -1028,6 +1087,8 @@ class BaseConnection(object): while i <= max_loops: new_data = self.read_channel() if new_data: + if self.ansi_escape_codes: + new_data = self.strip_ansi_escape_codes(new_data) output += new_data try: lines = output.split(self.RETURN) @@ -1043,8 +1104,8 @@ class BaseConnection(object): pass if re.search(search_pattern, output): break - else: - time.sleep(delay_factor * loop_delay) + + time.sleep(delay_factor * loop_delay) i += 1 else: # nobreak raise IOError("Search pattern never detected in send_command_expect: {}".format( @@ -1225,7 +1286,7 @@ class BaseConnection(object): output = self.read_until_pattern(pattern=pattern) if self.check_config_mode(): raise ValueError("Failed to exit configuration mode") - log.debug("exit_config_mode: {0}".format(output)) + log.debug("exit_config_mode: {}".format(output)) return output def send_config_from_file(self, config_file=None, **kwargs): @@ -1292,7 +1353,10 @@ class BaseConnection(object): output = self.config_mode(*cfg_mode_args) for cmd in config_commands: self.write_channel(self.normalize_cmd(cmd)) - time.sleep(delay_factor * .5) + if self.fast_cli: + pass + else: + time.sleep(delay_factor * .05) # Gather output output += self._read_channel_timing(delay_factor=delay_factor, max_loops=max_loops) @@ -1331,9 +1395,9 @@ class BaseConnection(object): :param string_buffer: The string to be processed to remove ANSI escape codes :type string_buffer: str - """ + """ # noqa log.debug("In strip_ansi_escape_codes") - log.debug("repr = {0}".format(repr(string_buffer))) + log.debug("repr = {}".format(repr(string_buffer))) code_position_cursor = chr(27) + r'\[\d+;\d+H' code_show_cursor = chr(27) + r'\[\?25h' @@ -1346,16 +1410,20 @@ class BaseConnection(object): code_carriage_return = chr(27) + r'\[1M' code_disable_line_wrapping = chr(27) + r'\[\?7l' code_reset_mode_screen_options = chr(27) + r'\[\?\d+l' + code_reset_graphics_mode = chr(27) + r'\[00m' code_erase_display = chr(27) + r'\[2J' code_graphics_mode = chr(27) + r'\[\d\d;\d\dm' code_graphics_mode2 = chr(27) + r'\[\d\d;\d\d;\d\dm' code_get_cursor_position = chr(27) + r'\[6n' + code_cursor_position = chr(27) + r'\[m' + code_erase_display = chr(27) + r'\[J' code_set = [code_position_cursor, code_show_cursor, code_erase_line, code_enable_scroll, code_erase_start_line, code_form_feed, code_carriage_return, code_disable_line_wrapping, code_erase_line_end, - code_reset_mode_screen_options, code_erase_display, - code_graphics_mode, code_graphics_mode2, code_get_cursor_position] + code_reset_mode_screen_options, code_reset_graphics_mode, code_erase_display, + code_graphics_mode, code_graphics_mode2, code_get_cursor_position, + code_cursor_position, code_erase_display] output = string_buffer for ansi_esc_code in code_set: @@ -1373,19 +1441,28 @@ class BaseConnection(object): """Any needed cleanup before closing connection.""" pass + def paramiko_cleanup(self): + """Cleanup Paramiko to try to gracefully handle SSH session ending.""" + self.remote_conn_pre.close() + del self.remote_conn_pre + def disconnect(self): """Try to gracefully close the SSH connection.""" try: self.cleanup() if self.protocol == 'ssh': - self.remote_conn_pre.close() - elif self.protocol == 'telnet' or 'serial': + self.paramiko_cleanup() + elif self.protocol == 'telnet': + self.remote_conn.close() + elif self.protocol == 'serial': self.remote_conn.close() except Exception: # There have been race conditions observed on disconnect. pass finally: + self.remote_conn_pre = None self.remote_conn = None + self.close_session_log() def commit(self): """Commit method for platforms that support this.""" @@ -1395,6 +1472,20 @@ class BaseConnection(object): """Not Implemented""" raise NotImplementedError + def open_session_log(self, filename, mode="write"): + """Open the session_log file.""" + if mode == 'append': + self.session_log = open(filename, mode="ab") + else: + self.session_log = open(filename, mode="wb") + self._session_log_close = True + + def close_session_log(self): + """Close the session_log file (if it is a file that we opened).""" + if self.session_log is not None and self._session_log_close: + self.session_log.close() + self.session_log = None + class TelnetConnection(BaseConnection): pass diff --git a/netmiko/calix/calix_b6.py b/netmiko/calix/calix_b6.py index c8b9b1d4079a851c97f9ea2226f34e11d4d6cc69..bd9bbb355cdd3170e9aa0df3714ae2ecf04700bd 100644 --- a/netmiko/calix/calix_b6.py +++ b/netmiko/calix/calix_b6.py @@ -21,7 +21,7 @@ class CalixB6Base(CiscoSSHConnection): def __init__(self, *args, **kwargs): default_enter = kwargs.get('default_enter') kwargs['default_enter'] = '\r\n' if default_enter is None else default_enter - super(CalixB6SSH, self).__init__(*args, **kwargs) + super(CalixB6Base, self).__init__(*args, **kwargs) def session_preparation(self): """Prepare the session after the connection has been established.""" diff --git a/netmiko/cisco/cisco_wlc_ssh.py b/netmiko/cisco/cisco_wlc_ssh.py index 2e65241bd5ccab14b6d75553ea4e297d69bf94b5..d903321eca4a06865b8e8074960ec58c6d1ab888 100644 --- a/netmiko/cisco/cisco_wlc_ssh.py +++ b/netmiko/cisco/cisco_wlc_ssh.py @@ -159,6 +159,18 @@ class CiscoWlcSSH(BaseConnection): log.debug("{}".format(output)) return output - def save_config(self, cmd='save config', confirm=True, confirm_response='y'): - return super(CiscoWlcSSH, self).save_config(cmd=cmd, confirm=confirm, - confirm_response=confirm_response) + def save_config(self, cmd='save config', confirm=True, + confirm_response='y'): + """Saves Config.""" + self.enable() + if confirm: + output = self.send_command_timing(command_string=cmd) + if confirm_response: + output += self.send_command_timing(confirm_response) + else: + # Send enter by default + output += self.send_command_timing(self.RETURN) + else: + # Some devices are slow so match on trailing-prompt if you can + output = self.send_command(command_string=cmd) + return output diff --git a/netmiko/cisco_base_connection.py b/netmiko/cisco_base_connection.py index d90f2a3b1fc0c11a67fbcc8e5386f1c2fa7efd2c..cba487fadcf73532168be83d679b741751f283ce 100644 --- a/netmiko/cisco_base_connection.py +++ b/netmiko/cisco_base_connection.py @@ -41,15 +41,13 @@ class CiscoBaseConnection(BaseConnection): return super(CiscoBaseConnection, self).config_mode(config_command=config_command, pattern=pattern) - def exit_config_mode(self, exit_config='end', pattern=''): + def exit_config_mode(self, exit_config='end', pattern='#'): """Exit from configuration mode.""" - if not pattern: - pattern = re.escape(self.base_prompt[:16]) return super(CiscoBaseConnection, self).exit_config_mode(exit_config=exit_config, pattern=pattern) def serial_login(self, pri_prompt_terminator=r'#\s*$', alt_prompt_terminator=r'>\s*$', - username_pattern=r"(?:[Uu]ser:|sername|ogin)", pwd_pattern=r"assword", + username_pattern=r"(?:user:|username|login)", pwd_pattern=r"assword", delay_factor=1, max_loops=20): self.write_channel(self.TELNET_RETURN) output = self.read_channel() @@ -61,7 +59,7 @@ class CiscoBaseConnection(BaseConnection): username_pattern, pwd_pattern, delay_factor, max_loops) def telnet_login(self, pri_prompt_terminator=r'#\s*$', alt_prompt_terminator=r'>\s*$', - username_pattern=r"(?:[Uu]ser:|sername|ogin|User Name)", + username_pattern=r"(?:user:|username|login|user name)", pwd_pattern=r"assword", delay_factor=1, max_loops=20): """Telnet login. Can be username/password or just password.""" @@ -77,14 +75,14 @@ class CiscoBaseConnection(BaseConnection): return_msg += output # Search for username pattern / send username - if re.search(username_pattern, output): + if re.search(username_pattern, output, flags=re.I): self.write_channel(self.username + self.TELNET_RETURN) time.sleep(1 * delay_factor) output = self.read_channel() return_msg += output # Search for password pattern / send password - if re.search(pwd_pattern, output): + if re.search(pwd_pattern, output, flags=re.I): self.write_channel(self.password + self.TELNET_RETURN) time.sleep(.5 * delay_factor) output = self.read_channel() @@ -109,7 +107,8 @@ class CiscoBaseConnection(BaseConnection): # Check for device with no password configured if re.search(r"assword required, but none set", output): - msg = "Telnet login failed - Password required, but none set: {}".format( + self.remote_conn.close() + msg = "Login failed - Password required, but none set: {}".format( self.host) raise NetMikoAuthenticationException(msg) @@ -122,7 +121,8 @@ class CiscoBaseConnection(BaseConnection): time.sleep(.5 * delay_factor) i += 1 except EOFError: - msg = "Telnet login failed: {}".format(self.host) + self.remote_conn.close() + msg = "Login failed: {}".format(self.host) raise NetMikoAuthenticationException(msg) # Last try to see if we already logged in @@ -134,7 +134,8 @@ class CiscoBaseConnection(BaseConnection): or re.search(alt_prompt_terminator, output, flags=re.M)): return return_msg - msg = "Telnet login failed: {}".format(self.host) + self.remote_conn.close() + msg = "Login failed: {}".format(self.host) raise NetMikoAuthenticationException(msg) def cleanup(self): @@ -144,6 +145,7 @@ class CiscoBaseConnection(BaseConnection): except Exception: # Always try to send 'exit' regardless of whether exit_config_mode works or not. pass + self._session_log_fin = True self.write_channel("exit" + self.RETURN) def _autodetect_fs(self, cmd='dir', pattern=r'Directory of (.*)/'): diff --git a/netmiko/citrix/__init__.py b/netmiko/citrix/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..cd2372af472a37496a3edaae63d3a94e3da7f468 --- /dev/null +++ b/netmiko/citrix/__init__.py @@ -0,0 +1,4 @@ +from __future__ import unicode_literals +from netmiko.citrix.netscaler_ssh import NetscalerSSH + +__all__ = ['NetscalerSSH'] diff --git a/netmiko/citrix/netscaler_ssh.py b/netmiko/citrix/netscaler_ssh.py new file mode 100644 index 0000000000000000000000000000000000000000..73b3cb84428caebe762b2b9638d74980362f5b66 --- /dev/null +++ b/netmiko/citrix/netscaler_ssh.py @@ -0,0 +1,59 @@ +import time + +from netmiko.base_connection import BaseConnection + + +class NetscalerSSH(BaseConnection): + """ Netscaler SSH class. """ + + def session_preparation(self): + """Prepare the session after the connection has been established.""" + # 0 will defer to the global delay factor + delay_factor = self.select_delay_factor(delay_factor=0) + self._test_channel_read() + self.set_base_prompt() + cmd = "{}set cli mode -page OFF{}".format(self.RETURN, self.RETURN) + self.disable_paging(command=cmd) + time.sleep(1 * delay_factor) + self.set_base_prompt() + time.sleep(.3 * delay_factor) + self.clear_buffer() + + def set_base_prompt(self, pri_prompt_terminator='#', + alt_prompt_terminator='>', delay_factor=1): + """Sets self.base_prompt. + + Netscaler has '>' for the prompt. + """ + prompt = self.find_prompt(delay_factor=delay_factor) + if not prompt[-1] in (pri_prompt_terminator, alt_prompt_terminator): + raise ValueError("Router prompt not found: {}".format(repr(prompt))) + + prompt = prompt.strip() + if len(prompt) == 1: + self.base_prompt = prompt + else: + # Strip off trailing terminator + self.base_prompt = prompt[:-1] + return self.base_prompt + + def check_config_mode(self): + """Netscaler devices do not have a config mode.""" + return False + + def config_mode(self): + """Netscaler devices do not have a config mode.""" + return '' + + def exit_config_mode(self): + """Netscaler devices do not have a config mode.""" + return '' + + def strip_prompt(self, a_string): + """ Strip 'Done' from command output """ + output = super(NetscalerSSH, self).strip_prompt(a_string) + lines = output.split(self.RESPONSE_RETURN) + if "Done" in lines[-1]: + return 'self.RESPONSE_RETURN'.join(lines[:-1]) + else: + return output diff --git a/netmiko/dell/__init__.py b/netmiko/dell/__init__.py index 40d7888500005769daf438c08c89920ab4a7f58c..c2958189cbe8013540e6da228d8bcdb8e9803005 100644 --- a/netmiko/dell/__init__.py +++ b/netmiko/dell/__init__.py @@ -1,6 +1,9 @@ from __future__ import unicode_literals from netmiko.dell.dell_force10_ssh import DellForce10SSH +from netmiko.dell.dell_os10_ssh import DellOS10SSH, DellOS10FileTransfer from netmiko.dell.dell_powerconnect import DellPowerConnectSSH from netmiko.dell.dell_powerconnect import DellPowerConnectTelnet +from netmiko.dell.dell_isilon_ssh import DellIsilonSSH -__all__ = ['DellForce10SSH', 'DellPowerConnectSSH', 'DellPowerConnectTelnet'] +__all__ = ['DellForce10SSH', 'DellPowerConnectSSH', 'DellPowerConnectTelnet', + 'DellOS10SSH', 'DellOS10FileTransfer', 'DellIsilonSSH'] diff --git a/netmiko/dell/dell_isilon_ssh.py b/netmiko/dell/dell_isilon_ssh.py new file mode 100644 index 0000000000000000000000000000000000000000..3e181cea341b4de75b365b0d39542fbff212179d --- /dev/null +++ b/netmiko/dell/dell_isilon_ssh.py @@ -0,0 +1,85 @@ +from __future__ import unicode_literals + +import time +import re + +from netmiko.base_connection import BaseConnection + + +class DellIsilonSSH(BaseConnection): + def set_base_prompt(self, pri_prompt_terminator='$', + alt_prompt_terminator='#', delay_factor=1): + """Determine base prompt.""" + return super(DellIsilonSSH, self).set_base_prompt( + pri_prompt_terminator=pri_prompt_terminator, + alt_prompt_terminator=alt_prompt_terminator, + delay_factor=delay_factor) + + def strip_ansi_escape_codes(self, string_buffer): + """Remove Null code""" + output = re.sub(r'\x00', '', string_buffer) + return super(DellIsilonSSH, self).strip_ansi_escape_codes(output) + + def session_preparation(self): + """Prepare the session after the connection has been established.""" + self.ansi_escape_codes = True + self.zsh_mode() + self.find_prompt(delay_factor=1) + self.set_base_prompt() + # Clear the read buffer + time.sleep(.3 * self.global_delay_factor) + self.clear_buffer() + + def zsh_mode(self, delay_factor=1, prompt_terminator="$"): + """Run zsh command to unify the environment""" + delay_factor = self.select_delay_factor(delay_factor) + self.clear_buffer() + command = self.RETURN + "zsh" + self.RETURN + self.write_channel(command) + time.sleep(1 * delay_factor) + self.set_prompt() + self.clear_buffer() + + def set_prompt(self, prompt_terminator="$"): + prompt = "PROMPT='%m{}'".format(prompt_terminator) + command = self.RETURN + prompt + self.RETURN + self.write_channel(command) + + def disable_paging(self, *args, **kwargs): + """Isilon doesn't have paging by default.""" + pass + + def check_enable_mode(self, *args, **kwargs): + """No enable mode on Isilon.""" + pass + + def enable(self, *args, **kwargs): + """No enable mode on Isilon.""" + pass + + def exit_enable_mode(self, *args, **kwargs): + """No enable mode on Isilon.""" + pass + + def check_config_mode(self, check_string='#'): + return super(DellIsilonSSH, self).check_config_mode(check_string=check_string) + + def config_mode(self, config_command='sudo su'): + """Attempt to become root.""" + delay_factor = self.select_delay_factor(delay_factor=1) + output = "" + if not self.check_config_mode(): + output += self.send_command_timing(config_command, strip_prompt=False, + strip_command=False) + if 'Password:' in output: + output = self.write_channel(self.normalize_cmd(self.secret)) + self.set_prompt(prompt_terminator="#") + time.sleep(1 * delay_factor) + self.set_base_prompt() + if not self.check_config_mode(): + raise ValueError("Failed to configuration mode") + return output + + def exit_config_mode(self, exit_config='exit'): + """Exit enable mode.""" + return super(DellIsilonSSH, self).exit_config_mode(exit_config=exit_config) diff --git a/netmiko/dell/dell_os10_ssh.py b/netmiko/dell/dell_os10_ssh.py new file mode 100644 index 0000000000000000000000000000000000000000..27f7d5f3660b6124e6df5827ab80f2c38d664f8b --- /dev/null +++ b/netmiko/dell/dell_os10_ssh.py @@ -0,0 +1,91 @@ +"""Dell EMC Networking OS10 Driver - supports dellos10.""" + +from netmiko.cisco_base_connection import CiscoSSHConnection +from netmiko.scp_handler import BaseFileTransfer +import os +import re + + +class DellOS10SSH(CiscoSSHConnection): + """Dell EMC Networking OS10 Driver - supports dellos10.""" + def save_config(self, cmd='copy running-configuration startup-configuration', confirm=False): + """Saves Config""" + return super(DellOS10SSH, self).save_config(cmd=cmd, confirm=confirm) + + +class DellOS10FileTransfer(BaseFileTransfer): + """Dell EMC Networking OS10 SCP File Transfer driver.""" + def __init__(self, ssh_conn, source_file, dest_file, file_system=None, direction='put'): + if file_system is None: + file_system = '/home/admin' + super(DellOS10FileTransfer, self).__init__(ssh_conn=ssh_conn, source_file=source_file, + dest_file=dest_file, file_system=file_system, + direction=direction) + self.folder_name = '/config' + + 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 + remote_cmd = 'system "ls -l {}/{}"'.format(self.file_system, remote_file) + remote_out = self.ssh_ctl_chan.send_command(remote_cmd) + for line in remote_out.splitlines(): + if remote_file in line: + file_size = line.split()[4] + break + 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_space_available(self, search_pattern=r"(\d+) bytes free"): + """Return space available on remote device.""" + remote_cmd = 'system "df {}"'.format(self.folder_name) + remote_output = self.ssh_ctl_chan.send_command_expect(remote_cmd) + for line in remote_output.splitlines(): + if self.folder_name in line: + space_available = line.split()[-3] + break + return int(space_available) + + @staticmethod + def process_md5(md5_output, pattern=r'(.*) (.*)'): + return super(DellOS10FileTransfer, DellOS10FileTransfer).process_md5(md5_output, + pattern=r'(.*) (.*)') + + def remote_md5(self, base_cmd='verify /md5', remote_file=None): + """Calculate remote MD5 and returns the hash. """ + 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 = 'system "md5sum {}/{}"'.format(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.strip() + + def check_file_exists(self, remote_cmd="dir home"): + """Check if the dest_file already exists on the file system (return boolean).""" + if self.direction == 'put': + remote_out = self.ssh_ctl_chan.send_command_expect(remote_cmd) + search_string = r"Directory contents .*{}".format(self.dest_file) + return bool(re.search(search_string, remote_out, flags=re.DOTALL)) + elif self.direction == 'get': + return os.path.exists(self.dest_file) + + def put_file(self): + """SCP copy the file from the local system to the remote device.""" + destination = "{}".format(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 get_file(self): + """SCP copy the file from the remote device to local system.""" + source_file = "{}".format(self.source_file) + self.scp_conn.scp_get_file(source_file, self.dest_file) + self.scp_conn.close() diff --git a/netmiko/dell/dell_powerconnect.py b/netmiko/dell/dell_powerconnect.py index f85ea22a4535c6b46aa134b0b55387c8b23d3c6b..04c8646ca51bddda5164a9c6bec3369778509bb0 100644 --- a/netmiko/dell/dell_powerconnect.py +++ b/netmiko/dell/dell_powerconnect.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from paramiko import SSHClient import time from os import path -from netmiko.cisco_base_connection import CiscoSSHConnection +from netmiko.cisco_base_connection import CiscoBaseConnection class SSHClient_noauth(SSHClient): @@ -12,13 +12,14 @@ class SSHClient_noauth(SSHClient): return -class DellPowerConnectBase(CiscoSSHConnection): +class DellPowerConnectBase(CiscoBaseConnection): """Dell PowerConnect Driver.""" 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.enable() self.disable_paging(command="terminal datadump") # Clear the read buffer time.sleep(.3 * self.global_delay_factor) @@ -100,14 +101,5 @@ class DellPowerConnectSSH(DellPowerConnectBase): class DellPowerConnectTelnet(DellPowerConnectBase): - 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.enable() - self.disable_paging(command="terminal length 0") - self.set_terminal_width() - # Clear the read buffer - time.sleep(.3 * self.global_delay_factor) - self.clear_buffer() + """Dell PowerConnect Telnet Driver.""" + pass diff --git a/netmiko/extreme/extreme_wing_ssh.py b/netmiko/extreme/extreme_wing_ssh.py index c026d7e21506d78c8a56703c249a114369b30cae..aabeb828d4fa7c0e04806b6cf759ce7f8a43b8b2 100644 --- a/netmiko/extreme/extreme_wing_ssh.py +++ b/netmiko/extreme/extreme_wing_ssh.py @@ -6,11 +6,10 @@ from netmiko.cisco_base_connection import CiscoSSHConnection class ExtremeWingSSH(CiscoSSHConnection): """Extreme WiNG support.""" def session_preparation(self): - self.set_base_prompt(pri_prompt_terminator='>', - alt_prompt_terminator='#', - delay_factor=2) + """Disable paging and set Max term width""" + self._test_channel_read(pattern=r">|#") + self.set_base_prompt() self.disable_paging(command="no page") self.set_terminal_width(command='terminal width 512') - # Clear the read buffer time.sleep(.3 * self.global_delay_factor) self.clear_buffer() diff --git a/netmiko/fortinet/fortinet_ssh.py b/netmiko/fortinet/fortinet_ssh.py index 793d28311b296a240e0455cc82f643fbd33ba298..f20231334f1f3a4dffb086717c2d3a33a95eebde 100644 --- a/netmiko/fortinet/fortinet_ssh.py +++ b/netmiko/fortinet/fortinet_ssh.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import paramiko import time +import re from netmiko.cisco_base_connection import CiscoSSHConnection @@ -28,6 +29,7 @@ class FortinetSSH(CiscoSSHConnection): output = self.send_command_timing(check_command) self.allow_disable_global = True self.vdoms = False + self._output_mode = 'more' if "Virtual domain configuration: enable" in output: self.vdoms = True @@ -40,6 +42,7 @@ class FortinetSSH(CiscoSSHConnection): new_output = '' if self.allow_disable_global: + self._retrieve_output_mode() disable_paging_commands = ["config system console", "set output standard", "end"] # There is an extra 'end' required if in multi-vdoms are enabled if self.vdoms: @@ -51,10 +54,24 @@ class FortinetSSH(CiscoSSHConnection): return output + new_output + def _retrieve_output_mode(self): + """Save the state of the output mode so it can be reset at the end of the session.""" + reg_mode = re.compile(r'output\s+:\s+(?P<mode>.*)\s+\n') + output = self.send_command("get system console") + result_mode_re = reg_mode.search(output) + if result_mode_re: + result_mode = result_mode_re.group('mode').strip() + if result_mode in ['more', 'standard']: + self._output_mode = result_mode + def cleanup(self): """Re-enable paging globally.""" if self.allow_disable_global: - enable_paging_commands = ["config system console", "set output more", "end"] + # Return paging state + output_mode_cmd = "set output {}".format(self._output_mode) + enable_paging_commands = ["config system console", + output_mode_cmd, + "end"] if self.vdoms: enable_paging_commands.insert(0, "config global") # Should test output is valid diff --git a/netmiko/hp/__init__.py b/netmiko/hp/__init__.py index 46a953d8defc3009cb70d4c80c478bb3252486fe..5eefcbc0c2cc12b1f2efb47193d12e4d31711445 100644 --- a/netmiko/hp/__init__.py +++ b/netmiko/hp/__init__.py @@ -1,5 +1,5 @@ from __future__ import unicode_literals -from netmiko.hp.hp_procurve_ssh import HPProcurveSSH -from netmiko.hp.hp_comware_ssh import HPComwareSSH +from netmiko.hp.hp_procurve import HPProcurveSSH, HPProcurveTelnet +from netmiko.hp.hp_comware import HPComwareSSH, HPComwareTelnet -__all__ = ['HPProcurveSSH', 'HPComwareSSH'] +__all__ = ['HPProcurveSSH', 'HPProcurveTelnet', 'HPComwareSSH', 'HPComwareTelnet'] diff --git a/netmiko/hp/hp_comware_ssh.py b/netmiko/hp/hp_comware.py similarity index 74% rename from netmiko/hp/hp_comware_ssh.py rename to netmiko/hp/hp_comware.py index 76d867084f33c9525c8ff0420154e2102ba014e4..18cc3c7dba927bf77b2a73e04ac2db9d1f995256 100644 --- a/netmiko/hp/hp_comware_ssh.py +++ b/netmiko/hp/hp_comware.py @@ -4,7 +4,7 @@ import time from netmiko.cisco_base_connection import CiscoSSHConnection -class HPComwareSSH(CiscoSSHConnection): +class HPComwareBase(CiscoSSHConnection): def session_preparation(self): """ @@ -32,15 +32,16 @@ class HPComwareSSH(CiscoSSHConnection): def config_mode(self, config_command='system-view'): """Enter configuration mode.""" - return super(HPComwareSSH, self).config_mode(config_command=config_command) + return super(HPComwareBase, self).config_mode(config_command=config_command) - def exit_config_mode(self, exit_config='return'): + def exit_config_mode(self, exit_config='return', pattern=r'>'): """Exit config mode.""" - return super(HPComwareSSH, self).exit_config_mode(exit_config=exit_config) + return super(HPComwareBase, self).exit_config_mode(exit_config=exit_config, + pattern=pattern) def check_config_mode(self, check_string=']'): """Check whether device is in configuration mode. Return a boolean.""" - return super(HPComwareSSH, self).check_config_mode(check_string=check_string) + return super(HPComwareBase, self).check_config_mode(check_string=check_string) def set_base_prompt(self, pri_prompt_terminator='>', alt_prompt_terminator=']', delay_factor=1): @@ -54,7 +55,7 @@ class HPComwareSSH(CiscoSSHConnection): This will be set on logging in, but not when entering system-view """ - prompt = super(HPComwareSSH, self).set_base_prompt( + prompt = super(HPComwareBase, self).set_base_prompt( pri_prompt_terminator=pri_prompt_terminator, alt_prompt_terminator=alt_prompt_terminator, delay_factor=delay_factor) @@ -79,4 +80,15 @@ class HPComwareSSH(CiscoSSHConnection): def save_config(self, cmd='save force', confirm=False): """Save Config.""" - return super(HPComwareSSH, self).save_config(cmd=cmd, confirm=confirm) + return super(HPComwareBase, self).save_config(cmd=cmd, confirm=confirm) + + +class HPComwareSSH(HPComwareBase): + pass + + +class HPComwareTelnet(HPComwareBase): + def __init__(self, *args, **kwargs): + default_enter = kwargs.get('default_enter') + kwargs['default_enter'] = '\r\n' if default_enter is None else default_enter + super(HPComwareTelnet, self).__init__(*args, **kwargs) diff --git a/netmiko/hp/hp_procurve_ssh.py b/netmiko/hp/hp_procurve.py similarity index 72% rename from netmiko/hp/hp_procurve_ssh.py rename to netmiko/hp/hp_procurve.py index fadaa6a592358368258546dbbcc3d14a24841610..3f30ebb9a22d8fa5d513ad63c505daed6c4c857f 100644 --- a/netmiko/hp/hp_procurve_ssh.py +++ b/netmiko/hp/hp_procurve.py @@ -7,7 +7,7 @@ from netmiko.cisco_base_connection import CiscoSSHConnection from netmiko import log -class HPProcurveSSH(CiscoSSHConnection): +class HPProcurveBase(CiscoSSHConnection): def session_preparation(self): """ @@ -44,6 +44,8 @@ class HPProcurveSSH(CiscoSSHConnection): def enable(self, cmd='enable', pattern='password', re_flags=re.IGNORECASE, default_username='manager'): """Enter enable mode""" + if self.check_enable_mode(): + return '' output = self.send_command_timing(cmd) if 'username' in output.lower() or 'login name' in output.lower() or \ 'user name' in output.lower(): @@ -63,10 +65,13 @@ class HPProcurveSSH(CiscoSSHConnection): time.sleep(.5) output = self.read_channel() if 'Do you want to log out' in output: + self._session_log_fin = True self.write_channel("y" + self.RETURN) # Don't automatically save the config (user's responsibility) elif 'Do you want to save the current' in output: + self._session_log_fin = True self.write_channel("n" + self.RETURN) + try: self.write_channel(self.RETURN) except socket.error: @@ -75,4 +80,22 @@ class HPProcurveSSH(CiscoSSHConnection): def save_config(self, cmd='write memory', confirm=False): """Save Config.""" - return super(HPProcurveSSH, self).save_config(cmd=cmd, confirm=confirm) + return super(HPProcurveBase, self).save_config(cmd=cmd, confirm=confirm) + + +class HPProcurveSSH(HPProcurveBase): + pass + + +class HPProcurveTelnet(HPProcurveBase): + def telnet_login(self, pri_prompt_terminator='#', alt_prompt_terminator='>', + username_pattern=r"Login Name:", pwd_pattern=r"assword", + delay_factor=1, max_loops=60): + """Telnet login: can be username/password or just password.""" + super(HPProcurveTelnet, self).telnet_login( + pri_prompt_terminator=pri_prompt_terminator, + alt_prompt_terminator=alt_prompt_terminator, + username_pattern=username_pattern, + pwd_pattern=pwd_pattern, + delay_factor=delay_factor, + max_loops=max_loops) diff --git a/netmiko/linux/linux_ssh.py b/netmiko/linux/linux_ssh.py index b08425e9962709e305759479878db92ce008a46b..44215f3efbf5d266629b82e0df5c0a33ca5947c7 100644 --- a/netmiko/linux/linux_ssh.py +++ b/netmiko/linux/linux_ssh.py @@ -93,6 +93,7 @@ class LinuxSSH(CiscoSSHConnection): def cleanup(self): """Try to Gracefully exit the SSH session.""" + self._session_log_fin = True self.write_channel("exit" + self.RETURN) def save_config(self, cmd='', confirm=True, confirm_response=''): diff --git a/netmiko/py23_compat.py b/netmiko/py23_compat.py index 570092e4b3c0ee6585453e67e840262a8565eaca..cda8d3f34f6cddeacb36142be765190c92f8f74b 100644 --- a/netmiko/py23_compat.py +++ b/netmiko/py23_compat.py @@ -2,6 +2,7 @@ from __future__ import print_function from __future__ import unicode_literals +import io import sys PY2 = sys.version_info.major == 2 @@ -10,6 +11,8 @@ PY3 = sys.version_info.major == 3 if PY3: string_types = (str,) text_type = str + bufferedio_types = io.BufferedIOBase else: string_types = (basestring,) # noqa text_type = unicode # noqa + bufferedio_types = (io.BufferedIOBase, file) # noqa diff --git a/netmiko/quanta/quanta_mesh_ssh.py b/netmiko/quanta/quanta_mesh_ssh.py index ddca0961e1beb175824fde02cea259ccfa38f70c..f98dcd7074eb5609ea048cd27f09f813af30b3bb 100644 --- a/netmiko/quanta/quanta_mesh_ssh.py +++ b/netmiko/quanta/quanta_mesh_ssh.py @@ -11,6 +11,6 @@ class QuantaMeshSSH(CiscoSSHConnection): """Enter configuration mode.""" return super(QuantaMeshSSH, self).config_mode(config_command=config_command) - def save_config(self, cmd='', confirm=True, confirm_response=''): - """Not Implemented""" - raise NotImplementedError + def save_config(self, cmd='copy running-config startup-config', confirm=False): + """Saves Config""" + return super(QuantaMeshSSH, self).save_config(cmd=cmd, confirm=confirm) diff --git a/netmiko/snmp_autodetect.py b/netmiko/snmp_autodetect.py index 2779224da30f05479384da99308e67b74db25977..29d819a2f0dd1ac3f2eb86ef0ef6de3ef8163dc8 100644 --- a/netmiko/snmp_autodetect.py +++ b/netmiko/snmp_autodetect.py @@ -36,6 +36,9 @@ SNMP_MAPPER_BASE = { 'arista_eos': {"oid": ".1.3.6.1.2.1.1.1.0", "expr": re.compile(r".*Arista Networks EOS.*", re.IGNORECASE), "priority": 99}, + 'paloalto_panos': {"oid": ".1.3.6.1.2.1.1.1.0", + "expr": re.compile(r".*Palo Alto Networks.*", re.IGNORECASE), + "priority": 99}, 'hp_comware': {"oid": ".1.3.6.1.2.1.1.1.0", "expr": re.compile(r".*HP Comware.*", re.IGNORECASE), "priority": 99}, diff --git a/netmiko/ssh_autodetect.py b/netmiko/ssh_autodetect.py index 89d0a4cf4eec182d865f54022c866987a01abf8f..8297f704dd5c99e0a57d83267f593f4d5970742f 100644 --- a/netmiko/ssh_autodetect.py +++ b/netmiko/ssh_autodetect.py @@ -52,12 +52,12 @@ from netmiko.base_connection import BaseConnection SSH_MAPPER_BASE = { 'alcatel_aos': { "cmd": "show system", - "search_patterns": ["Alcatel-Lucent"], + "search_patterns": [r"Alcatel-Lucent"], "priority": 99, "dispatch": "_autodetect_std", }, 'alcatel_sros': { - "cmd": "show version | match TiMOS", + "cmd": "show version", "search_patterns": [ "Nokia", "Alcatel", @@ -65,14 +65,20 @@ SSH_MAPPER_BASE = { "priority": 99, "dispatch": "_autodetect_std", }, + 'apresia_aeos': { + "cmd": "show system", + "search_patterns": ["Apresia"], + "priority": 99, + "dispatch": "_autodetect_std", + }, 'arista_eos': { - "cmd": "show version | inc rist", - "search_patterns": ["Arista"], + "cmd": "show version", + "search_patterns": [r"Arista"], "priority": 99, "dispatch": "_autodetect_std", }, 'cisco_ios': { - "cmd": "show version | inc Cisco", + "cmd": "show version", "search_patterns": [ "Cisco IOS Software", "Cisco Internetwork Operating System Software" @@ -81,45 +87,51 @@ SSH_MAPPER_BASE = { "dispatch": "_autodetect_std", }, 'cisco_asa': { - "cmd": "show version | inc Cisco", - "search_patterns": ["Cisco Adaptive Security Appliance", "Cisco ASA"], + "cmd": "show version", + "search_patterns": [r"Cisco Adaptive Security Appliance", r"Cisco ASA"], "priority": 99, "dispatch": "_autodetect_std", }, 'cisco_nxos': { - "cmd": "show version | inc Cisco", - "search_patterns": ["Cisco Nexus Operating System", "NX-OS"], + "cmd": "show version", + "search_patterns": [r"Cisco Nexus Operating System", r"NX-OS"], "priority": 99, "dispatch": "_autodetect_std", }, 'cisco_xr': { - "cmd": "show version | inc Cisco", - "search_patterns": ["Cisco IOS XR"], + "cmd": "show version", + "search_patterns": [r"Cisco IOS XR"], "priority": 99, "dispatch": "_autodetect_std", }, 'huawei': { - "cmd": "display version | inc Huawei", + "cmd": "display version", "search_patterns": [ - "Huawei Technologies", - "Huawei Versatile Routing Platform Software" + r"Huawei Technologies", + r"Huawei Versatile Routing Platform Software" ], "priority": 99, "dispatch": "_autodetect_std", }, 'juniper_junos': { - "cmd": "show version | match JUNOS", + "cmd": "show version", "search_patterns": [ - "JUNOS Software Release", - "JUNOS .+ Software", - "JUNOS OS Kernel", + r"JUNOS Software Release", + r"JUNOS .+ Software", + r"JUNOS OS Kernel", ], "priority": 99, "dispatch": "_autodetect_std", }, 'dell_force10': { - "cmd": "show version | grep Type", - "search_patterns": ["S4048-ON"], + "cmd": "show version", + "search_patterns": [r"S4048-ON"], + "priority": 99, + "dispatch": "_autodetect_std", + }, + 'dell_os10': { + "cmd": "show version", + "search_patterns": [r"Dell EMC Networking OS10-Enterprise"], "priority": 99, "dispatch": "_autodetect_std", }, @@ -267,6 +279,7 @@ class SSHDetect(object): if not cmd or not search_patterns: return 0 try: + # _send_command_wrapper will use already cached results if available response = self._send_command_wrapper(cmd) # Look for error conditions in output for pattern in invalid_responses: diff --git a/netmiko/ssh_dispatcher.py b/netmiko/ssh_dispatcher.py index 6309a9bef02c9bc7c33e6bbb50bebb9b7fea9155..987def3764e80e4444e6ecedf3b8729b6137fd93 100644 --- a/netmiko/ssh_dispatcher.py +++ b/netmiko/ssh_dispatcher.py @@ -7,6 +7,7 @@ from netmiko.alcatel import AlcatelAosSSH from netmiko.alcatel import AlcatelSrosSSH from netmiko.arista import AristaSSH, AristaTelnet from netmiko.arista import AristaFileTransfer +from netmiko.apresia import ApresiaAeosSSH, ApresiaAeosTelnet from netmiko.aruba import ArubaSSH from netmiko.avaya import AvayaErsSSH from netmiko.avaya import AvayaVspSSH @@ -23,10 +24,13 @@ from netmiko.cisco import CiscoS300SSH from netmiko.cisco import CiscoTpTcCeSSH from netmiko.cisco import CiscoWlcSSH from netmiko.cisco import CiscoXrSSH, CiscoXrFileTransfer +from netmiko.citrix import NetscalerSSH from netmiko.coriant import CoriantSSH from netmiko.dell import DellForce10SSH +from netmiko.dell import DellOS10SSH, DellOS10FileTransfer from netmiko.dell import DellPowerConnectSSH from netmiko.dell import DellPowerConnectTelnet +from netmiko.dell import DellIsilonSSH from netmiko.eltex import EltexSSH from netmiko.enterasys import EnterasysSSH from netmiko.extreme import ExtremeSSH @@ -34,7 +38,7 @@ from netmiko.extreme import ExtremeWingSSH from netmiko.extreme import ExtremeTelnet from netmiko.f5 import F5LtmSSH from netmiko.fortinet import FortinetSSH -from netmiko.hp import HPProcurveSSH, HPComwareSSH +from netmiko.hp import HPProcurveSSH, HPProcurveTelnet, HPComwareSSH, HPComwareTelnet from netmiko.huawei import HuaweiSSH, HuaweiVrpv8SSH from netmiko.juniper import JuniperSSH, JuniperTelnet from netmiko.juniper import JuniperFileTransfer @@ -60,6 +64,7 @@ CLASS_MAPPER_BASE = { 'accedian': AccedianSSH, 'alcatel_aos': AlcatelAosSSH, 'alcatel_sros': AlcatelSrosSSH, + 'apresia_aeos': ApresiaAeosSSH, 'arista_eos': AristaSSH, 'aruba_os': ArubaSSH, 'avaya_ers': AvayaErsSSH, @@ -82,7 +87,9 @@ CLASS_MAPPER_BASE = { 'cisco_xr': CiscoXrSSH, 'coriant': CoriantSSH, 'dell_force10': DellForce10SSH, + 'dell_os10': DellOS10SSH, 'dell_powerconnect': DellPowerConnectSSH, + 'dell_isilon': DellIsilonSSH, 'eltex': EltexSSH, 'enterasys': EnterasysSSH, 'extreme': ExtremeSSH, @@ -100,6 +107,7 @@ CLASS_MAPPER_BASE = { 'mellanox': MellanoxSSH, 'mrv_optiswitch': MrvOptiswitchSSH, 'netapp_cdot': NetAppcDotSSH, + 'netscaler': NetscalerSSH, 'ovs_linux': OvsLinuxSSH, 'paloalto_panos': PaloAltoPanosSSH, 'pluribus': PluribusSSH, @@ -115,6 +123,7 @@ FILE_TRANSFER_MAP = { 'arista_eos': AristaFileTransfer, 'cisco_asa': CiscoAsaFileTransfer, 'cisco_ios': CiscoIosFileTransfer, + 'dell_os10': DellOS10FileTransfer, 'cisco_nxos': CiscoNxosFileTransfer, 'cisco_xe': CiscoIosFileTransfer, 'cisco_xr': CiscoXrFileTransfer, @@ -141,7 +150,10 @@ FILE_TRANSFER_MAP = new_mapper CLASS_MAPPER['brocade_fastiron_telnet'] = RuckusFastironTelnet CLASS_MAPPER['brocade_netiron_telnet'] = BrocadeNetironTelnet CLASS_MAPPER['cisco_ios_telnet'] = CiscoIosTelnet +CLASS_MAPPER['apresia_aeos_telnet'] = ApresiaAeosTelnet CLASS_MAPPER['arista_eos_telnet'] = AristaTelnet +CLASS_MAPPER['hp_procurve_telnet'] = HPProcurveTelnet +CLASS_MAPPER['hp_comware_telnet'] = HPComwareTelnet CLASS_MAPPER['juniper_junos_telnet'] = JuniperTelnet CLASS_MAPPER['calix_b6_telnet'] = CalixB6Telnet CLASS_MAPPER['dell_powerconnect_telnet'] = DellPowerConnectTelnet diff --git a/netmiko/vyos/vyos_ssh.py b/netmiko/vyos/vyos_ssh.py index f44b6e26580a83cf639b6c532455fd0d9d24eb85..8799499a39c8df3425b28e14685a598fabe8464a 100644 --- a/netmiko/vyos/vyos_ssh.py +++ b/netmiko/vyos/vyos_ssh.py @@ -62,7 +62,7 @@ class VyOSSSH(CiscoSSHConnection): """ delay_factor = self.select_delay_factor(delay_factor) - error_marker = 'Failed to generate committed config' + error_marker = ['Failed to generate committed config', 'Commit failed'] command_string = 'commit' if comment: @@ -72,7 +72,7 @@ class VyOSSSH(CiscoSSHConnection): output += self.send_command_expect(command_string, strip_prompt=False, strip_command=False, delay_factor=delay_factor) - if error_marker in output: + if any(x in output for x in error_marker): raise ValueError('Commit failed with following errors:\n\n{}'.format(output)) return output diff --git a/requirements.txt b/requirements.txt index 0a7f52f20406c7cf67bb7ea0e0f26ffaf564ce23..cd8c47d3d7e46ef695ddb5cc3b9c2d4dfc633d8d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,3 @@ -paramiko>=2.0.0 -scp>=0.10.0 -pyyaml -pyserial -textfsm +--index-url https://pypi.python.org/simple/ + +-e . diff --git a/setup.cfg b/setup.cfg index 8ba5463ba2c6f62ac6bfc6282a1fd0dcdd164c81..fcd66a95e853af75e5b7fae44ed06e24f58b3152 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [pylama] linters = mccabe,pep8,pyflakes ignore = D203,C901 -skip = tests/*,build/*,.tox/*,netmiko/_textfsm/* +skip = tests/*,build/*,.tox/*,netmiko/_textfsm/*,examples/use_cases/* [pylama:pep8] max_line_length = 100 diff --git a/setup.py b/setup.py index 6330dd553a0aca293942ec40914d3ceece02dd41..d0e25269c9e4e0fccb2ecc7c7b8c824536523f24 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,13 @@ setup( 'Programming Language :: Python :: 3.6', ], packages=find_packages(exclude=("test*", )), - install_requires=['paramiko>=2.0.0', 'scp>=0.10.0', 'pyyaml', 'pyserial', 'textfsm'], + install_requires=[ + 'paramiko>=2.0.0', + 'scp>=0.10.0', + 'pyyaml', + 'pyserial', + 'textfsm' + ], extras_require={ 'test': ['pytest>=3.2.5', ] }, diff --git a/tests/SLOG/cisco881_slog.log b/tests/SLOG/cisco881_slog.log new file mode 100644 index 0000000000000000000000000000000000000000..358220887d430166e4303aeb44e499ffa15a9c46 --- /dev/null +++ b/tests/SLOG/cisco881_slog.log @@ -0,0 +1,20 @@ +pynet-rtr1# + +pynet-rtr1#terminal length 0 +terminal length 0 +pynet-rtr1#terminal width 511 +terminal width 511 +pynet-rtr1# + +pynet-rtr1#show ip interface brief +show ip interface brief +Interface IP-Address OK? Method Status Protocol +FastEthernet0 unassigned YES unset down down +FastEthernet1 unassigned YES unset down down +FastEthernet2 unassigned YES unset down down +FastEthernet3 unassigned YES unset down down +FastEthernet4 10.220.88.20 YES NVRAM up up +Vlan1 unassigned YES unset down down +pynet-rtr1# + +pynet-rtr1#exit diff --git a/tests/SLOG/cisco881_slog_append.log b/tests/SLOG/cisco881_slog_append.log new file mode 100644 index 0000000000000000000000000000000000000000..b4fcf3a2df129c101e948b12aff951885dda1f8c --- /dev/null +++ b/tests/SLOG/cisco881_slog_append.log @@ -0,0 +1,16 @@ +Initial file contents + +pynet-rtr1# +pynet-rtr1#terminal length 0 +pynet-rtr1#terminal width 511 +pynet-rtr1# +pynet-rtr1#show ip interface brief +Interface IP-Address OK? Method Status Protocol +FastEthernet0 unassigned YES unset down down +FastEthernet1 unassigned YES unset down down +FastEthernet2 unassigned YES unset down down +FastEthernet3 unassigned YES unset down down +FastEthernet4 10.220.88.20 YES NVRAM up up +Vlan1 unassigned YES unset down down +pynet-rtr1# +pynet-rtr1#exit diff --git a/tests/SLOG/cisco881_slog_append_compare.log b/tests/SLOG/cisco881_slog_append_compare.log new file mode 100644 index 0000000000000000000000000000000000000000..8d9f79606ccb15d899dd2e52609e7bf98707acac --- /dev/null +++ b/tests/SLOG/cisco881_slog_append_compare.log @@ -0,0 +1,14 @@ +pynet-rtr1# +pynet-rtr1#terminal length 0 +pynet-rtr1#terminal width 511 +pynet-rtr1# +pynet-rtr1#show ip interface brief +Interface IP-Address OK? Method Status Protocol +FastEthernet0 unassigned YES unset down down +FastEthernet1 unassigned YES unset down down +FastEthernet2 unassigned YES unset down down +FastEthernet3 unassigned YES unset down down +FastEthernet4 10.220.88.20 YES NVRAM up up +Vlan1 unassigned YES unset down down +pynet-rtr1# +pynet-rtr1#exit diff --git a/tests/SLOG/cisco881_slog_compare.log b/tests/SLOG/cisco881_slog_compare.log new file mode 100644 index 0000000000000000000000000000000000000000..8d9f79606ccb15d899dd2e52609e7bf98707acac --- /dev/null +++ b/tests/SLOG/cisco881_slog_compare.log @@ -0,0 +1,14 @@ +pynet-rtr1# +pynet-rtr1#terminal length 0 +pynet-rtr1#terminal width 511 +pynet-rtr1# +pynet-rtr1#show ip interface brief +Interface IP-Address OK? Method Status Protocol +FastEthernet0 unassigned YES unset down down +FastEthernet1 unassigned YES unset down down +FastEthernet2 unassigned YES unset down down +FastEthernet3 unassigned YES unset down down +FastEthernet4 10.220.88.20 YES NVRAM up up +Vlan1 unassigned YES unset down down +pynet-rtr1# +pynet-rtr1#exit diff --git a/tests/SLOG/cisco881_slog_wr_compare.log b/tests/SLOG/cisco881_slog_wr_compare.log new file mode 100644 index 0000000000000000000000000000000000000000..358220887d430166e4303aeb44e499ffa15a9c46 --- /dev/null +++ b/tests/SLOG/cisco881_slog_wr_compare.log @@ -0,0 +1,20 @@ +pynet-rtr1# + +pynet-rtr1#terminal length 0 +terminal length 0 +pynet-rtr1#terminal width 511 +terminal width 511 +pynet-rtr1# + +pynet-rtr1#show ip interface brief +show ip interface brief +Interface IP-Address OK? Method Status Protocol +FastEthernet0 unassigned YES unset down down +FastEthernet1 unassigned YES unset down down +FastEthernet2 unassigned YES unset down down +FastEthernet3 unassigned YES unset down down +FastEthernet4 10.220.88.20 YES NVRAM up up +Vlan1 unassigned YES unset down down +pynet-rtr1# + +pynet-rtr1#exit diff --git a/tests/conftest.py b/tests/conftest.py index 7baa85f32e16003a4843a538d81e57a6f1cd4186..32f1b069fe8b9526bcdd4712d8af654f29a2acb9 100755 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -48,6 +48,37 @@ def net_connect_cm(request): return my_prompt +@pytest.fixture(scope='module') +def net_connect_slog_wr(request): + """ + Create the SSH connection to the remote device. Modify session_log init arguments. + + Return the netmiko connection object. + """ + 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 + device['session_log_record_writes'] = True + conn = ConnectHandler(**device) + return conn + + +@pytest.fixture(scope='module') +def device_slog(request): + """ + Create the SSH connection to the remote device. Modify session_log init arguments. + + Return the netmiko device (not connected) + """ + 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 + device['session_log_file_mode'] = 'append' + return device + + @pytest.fixture(scope='module') def expected_responses(request): ''' @@ -113,6 +144,18 @@ def delete_file_ios(ssh_conn, dest_file_system, dest_file): raise ValueError("An error happened deleting file on Cisco IOS") +def delete_file_dellos10(ssh_conn, dest_file_system, dest_file): + """Delete a remote file for a Dell OS10 device.""" + if not dest_file: + raise ValueError("Invalid dest file specified") + + cmd = "delete home://{}".format(dest_file) + output = ssh_conn.send_command_timing(cmd) + if "Proceed to delete" in output: + output = ssh_conn.send_command_timing("yes") + return output + + raise ValueError("An error happened deleting file on Dell OS10") def delete_file_generic(ssh_conn, dest_file_system, dest_file): """Delete a remote file for a Junos device.""" @@ -337,5 +380,11 @@ def get_platform_args(): 'enable_scp': False, 'delete_file': delete_file_generic, }, + + 'dellos10': { + 'file_system': '/home/admin', + 'enable_scp': False, + 'delete_file': delete_file_dellos10, + }, } diff --git a/tests/etc/commands.yml.example b/tests/etc/commands.yml.example index e3ef91cb1787aeaeddafc0855bc98f678d77f210..4715ee8d1395bc08b45d4ac994cf79a6ebd2871a 100644 --- a/tests/etc/commands.yml.example +++ b/tests/etc/commands.yml.example @@ -126,6 +126,16 @@ ubiquiti_edge: - "logging persistent 4" config_verification: "show running-config | include 'logging'" +dellos10: + version: "show version" + basic: "show ip interface brief" + extended_output: "show running-config" + config: + - "host-description test" + - "no host-description" + - "host-description node" + config_verification: "show running-config" + dell_powerconnect: version: "show version" basic: "show ip interface vlan 1" @@ -153,6 +163,11 @@ alcatel_aos: config: - 'VLAN 666' config_verification: "show vlan" + +netscaler: + version: "show version" + basic: "show ip -summary" + extended_output: "show interfaces" calix_b6_ssh: version: "show version" diff --git a/tests/etc/responses.yml.example b/tests/etc/responses.yml.example index 12ffc6b8262dbcf29fdb49b09516809e1f4a4563..d900fbc6e7052928cda66e3fa6f0e428cb6ae529 100644 --- a/tests/etc/responses.yml.example +++ b/tests/etc/responses.yml.example @@ -78,6 +78,17 @@ ubiquiti_edge: cmd_response_init: "" cmd_response_final: "logging persistent 4" +dellos10: + base_prompt: OS10 + router_prompt : OS10# + enable_prompt: OS10# + interface_ip: 192.168.23.129 + version_banner: "Dell EMC Networking OS10-Enterprise" + multiple_line_output: "Last configuration change" + cmd_response_init: "host-description test" + cmd_response_final: "host-description node" + scp_remote_space: 1000 + dell_powerconnect: base_prompt: myswitch router_prompt : myswitch# @@ -104,6 +115,14 @@ alcatel_aos: version_banner: "Alcatel-Lucent OS6250-24 6.7.1.108.R04 Service Release, January 04, 2017" multiple_line_output: "FC - ForcedCopper PC - PreferredCopper C - Copper" +netscaler: + base_prompt: ">" + router_prompt: ">" + enable_prompt: ">" + interface_ip: "192.168.10.10" + multiple_line_output: "Netscaler Loopback interface" + version_banner: "NetScaler" + calix_b6_ssh: base_prompt: CALIX-B6-TEST router_prompt: CALIX-B6-TEST> @@ -112,4 +131,4 @@ calix_b6_ssh: version_banner: "Kernel build id 8.0.30.0" multiple_line_output: "rtcPwrUptimeTotal" cmd_response_init: "Building configuration... Done" - cmd_response_final: "access-list ethernet 999 permit ip" \ No newline at end of file + cmd_response_final: "access-list ethernet 999 permit ip" diff --git a/tests/etc/test_devices.yml.example b/tests/etc/test_devices.yml.example index 5c7a041331d992f0da2d55c9c9fb7c5827286ec9..d322da5be16dcc14ca862437fd284a21795b8af7 100644 --- a/tests/etc/test_devices.yml.example +++ b/tests/etc/test_devices.yml.example @@ -89,6 +89,12 @@ dell_powerconnect: username: admin password: admin +dellos10: + device_type: dellos10 + ip: 192.168.23.129 + username: admin + password: admin + pluribus: device_type: pluribus_ssh ip: 172.17.19.1 @@ -100,10 +106,16 @@ alcatel_aos: ip: 192.168.1.154 username: admin password: switch + +netscaler: + device_type: netscaler + ip: 192.168.10.10 + username: admin + password: admin calix_b6_ssh: device_type: calix_b6_ssh ip: 192.168.99.99 username: cli password: occam - secret: razor \ No newline at end of file + secret: razor diff --git a/tests/test_apresia_aeos.sh b/tests/test_apresia_aeos.sh new file mode 100755 index 0000000000000000000000000000000000000000..ecdecdccc49a4b08bc0b6aecb743918ef428093a --- /dev/null +++ b/tests/test_apresia_aeos.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +RETURN_CODE=0 + +# Exit on the first test failure and set RETURN_CODE = 1 +echo "Starting tests...good luck:" \ +&& py.test -v test_netmiko_show.py --test_device apresia_aeos_telnet \ +&& py.test -v test_netmiko_config.py --test_device apresia_aeos_telnet \ +|| RETURN_CODE=1 + +exit $RETURN_CODE diff --git a/tests/test_cisco.sh b/tests/test_cisco.sh index cb4e5f3417bd8152cd10647a6431af5d7b8d712a..4d4f6b6f238df9d91de37c029b1f437c390b5290 100755 --- a/tests/test_cisco.sh +++ b/tests/test_cisco.sh @@ -4,9 +4,11 @@ RETURN_CODE=0 # Exit on the first test failure and set RETURN_CODE = 1 echo "Cisco IOS SSH" \ -&& py.test -v test_netmiko_show.py --test_device cisco881_ssh_config \ -&& py.test -v test_netmiko_config.py --test_device cisco881_ssh_config \ +&& date \ +&& py.test -v test_netmiko_show.py --test_device cisco881_fast \ +&& date \ +&& py.test -v test_netmiko_config.py --test_device cisco881_fast \ +&& date \ || RETURN_CODE=1 exit $RETURN_CODE - diff --git a/tests/test_dellos10.sh b/tests/test_dellos10.sh new file mode 100755 index 0000000000000000000000000000000000000000..f4ab8422497ec4665507be328fd442d84c1f5e83 --- /dev/null +++ b/tests/test_dellos10.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +RETURN_CODE=0 + +# Exit on the first test failure and set RETURN_CODE = 1 +echo "Starting tests...good luck:" \ +&& echo "Dell EMC Networking dellos10" \ +&& py.test -v test_netmiko_show.py --test_device dellos10 \ +&& py.test -v test_netmiko_config.py --test_device dellos10 \ +&& py.test -v test_netmiko_scp.py --test_device dellos10 \ +|| RETURN_CODE=1 + +exit $RETURN_CODE diff --git a/tests/test_hp_comware_telnet.sh b/tests/test_hp_comware_telnet.sh new file mode 100755 index 0000000000000000000000000000000000000000..e55e048358698bf7b82891f4b60adaede6198ecf --- /dev/null +++ b/tests/test_hp_comware_telnet.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +RETURN_CODE=0 + +# Exit on the first test failure and set RETURN_CODE = 1 +echo "Starting tests...good luck:" \ +&& py.test -v test_netmiko_show.py --test_device hp_comware_telnet \ +&& py.test -v test_netmiko_config.py --test_device hp_comware_telnet \ +|| RETURN_CODE=1 + +exit $RETURN_CODE diff --git a/tests/test_hp_telnet.sh b/tests/test_hp_telnet.sh new file mode 100644 index 0000000000000000000000000000000000000000..625773c8110a30dd7f552543bb88f28af2adacef --- /dev/null +++ b/tests/test_hp_telnet.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +RETURN_CODE=0 + +# Exit on the first test failure and set RETURN_CODE = 1 +echo "Starting tests...good luck:" \ +&& py.test -v test_netmiko_show.py --test_device hp_procurve_telnet \ +&& py.test -v test_netmiko_config.py --test_device hp_procurve_telnet \ +|| RETURN_CODE=1 + +exit $RETURN_CODE \ No newline at end of file diff --git a/tests/test_netmiko_sesssion_log.py b/tests/test_netmiko_sesssion_log.py new file mode 100755 index 0000000000000000000000000000000000000000..e04d9f1d98ea589f773d2e9778c0313a94214e6b --- /dev/null +++ b/tests/test_netmiko_sesssion_log.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +from __future__ import print_function +from __future__ import unicode_literals +import time +import hashlib +import io +from netmiko import ConnectHandler + + +def calc_md5(file_name=None, contents=None): + """Compute MD5 hash of file.""" + if contents is not None: + pass + elif file_name: + with open(file_name, "rb") as f: + contents = f.read() + else: + raise ValueError("Most specify either file_name or contents") + + return hashlib.md5(contents).hexdigest() + + +def read_session_log(session_file, append=False): + """Leading white-space can vary. Strip off leading white-space.""" + with open(session_file, "rb") as f: + if append is True: + line = f.readline().decode() + assert 'Initial file contents' in line + log_content = f.read().lstrip() + return log_content + + +def session_action(my_connect, command): + """Common actions in the netmiko session to generate the session log.""" + time.sleep(1) + my_connect.clear_buffer() + output = my_connect.send_command(command) + my_connect.disconnect() + return output + + +def session_log_md5(session_file, compare_file): + """Compare the session_log MD5 to the compare_file MD5""" + compare_log_md5 = calc_md5(file_name=compare_file) + log_content = read_session_log(session_file) + session_log_md5 = calc_md5(contents=log_content) + assert session_log_md5 == compare_log_md5 + + +def session_log_md5_append(session_file, compare_file): + """Compare the session_log MD5 to the compare_file MD5""" + compare_log_md5 = calc_md5(file_name=compare_file) + log_content = read_session_log(session_file, append=True) + session_log_md5 = calc_md5(contents=log_content) + assert session_log_md5 == compare_log_md5 + + +def test_session_log(net_connect, commands, expected_responses): + """Verify session_log matches expected content.""" + command = commands["basic"] + session_action(net_connect, command) + + compare_file = expected_responses['compare_log'] + session_file = expected_responses['session_log'] + + session_log_md5(session_file, compare_file) + + +def test_session_log_write(net_connect_slog_wr, commands, expected_responses): + """Verify session_log matches expected content, but when channel writes are also logged.""" + command = commands["basic"] + session_action(net_connect_slog_wr, command) + + compare_file = expected_responses['compare_log_wr'] + session_file = expected_responses['session_log'] + session_log_md5(session_file, compare_file) + + +def test_session_log_append(device_slog, commands, expected_responses): + """Verify session_log matches expected content, but when channel writes are also logged.""" + session_file = expected_responses['session_log_append'] + # Create a starting file + with open(session_file, "wb") as f: + f.write(b"Initial file contents\n\n") + + # The netmiko connection has not been established yet. + device_slog['session_log'] = session_file + + conn = ConnectHandler(**device_slog) + command = commands["basic"] + session_action(conn, command) + + compare_file = expected_responses['compare_log_append'] + session_log_md5_append(session_file, compare_file) + + +def test_session_log_bytesio(device_slog, commands, expected_responses): + """Verify session_log matches expected content, but when channel writes are also logged.""" + s_log = io.BytesIO() + + # The netmiko connection has not been established yet. + device_slog['session_log'] = s_log + device_slog['session_log_file_mode'] = 'write' + + conn = ConnectHandler(**device_slog) + command = commands["basic"] + session_action(conn, command) + + compare_file = expected_responses['compare_log'] + compare_log_md5 = calc_md5(file_name=compare_file) + + log_content = s_log.getvalue() + session_log_md5 = calc_md5(contents=log_content) + assert session_log_md5 == compare_log_md5 diff --git a/tests/test_netmiko_show.py b/tests/test_netmiko_show.py index 9f9b7c416b2314656b85c9649f8c716c603f3b5e..bf5359b7ba066d3896a374a4ae1c3fb824efe1a3 100755 --- a/tests/test_netmiko_show.py +++ b/tests/test_netmiko_show.py @@ -63,6 +63,8 @@ def test_base_prompt(net_connect, commands, expected_responses): def test_strip_prompt(net_connect, commands, expected_responses): """Ensure the router prompt is not in the command output.""" + if expected_responses['base_prompt'] == '': + return show_ip = net_connect.send_command_timing(commands["basic"]) show_ip_alt = net_connect.send_command_expect(commands["basic"]) assert expected_responses['base_prompt'] not in show_ip diff --git a/tests/test_suite_alt.sh b/tests/test_suite_alt.sh index d81d8ef9dbcf323fb5ae68459830f25796321639..c84b6e424638c8c3045a4fca317db854db126e0e 100755 --- a/tests/test_suite_alt.sh +++ b/tests/test_suite_alt.sh @@ -15,6 +15,13 @@ echo "Starting tests...good luck:" \ && py.test -v test_netmiko_tcl.py --test_device cisco881 \ && py.test -v test_netmiko_show.py --test_device cisco881 \ && py.test -v test_netmiko_config.py --test_device cisco881 \ +&& py.test -v test_netmiko_sesssion_log.py --test_device cisco881_slog \ +\ +&& echo "Cisco IOS SSH fast_cli (including SCP)" \ +&& py.test -v test_netmiko_scp.py --test_device cisco881_fast \ +&& py.test -v test_netmiko_tcl.py --test_device cisco881_fast \ +&& py.test -v test_netmiko_show.py --test_device cisco881_fast \ +&& py.test -v test_netmiko_config.py --test_device cisco881_fast \ \ && echo "Cisco IOS using SSH config with SSH Proxy" \ && py.test -v test_netmiko_show.py --test_device cisco881_ssh_config \