diff --git a/README.md b/README.md index a421583eb..580c41c3f 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,15 @@ Netmiko Multi-vendor library to simplify Paramiko SSH connections to network devices -Python 2.7, 3.4, 3.5 +Python 2.7, 3.5, 3.6 #### Requires: -Paramiko >= 1.13+ +Paramiko >= 2 scp >= 0.10.0 pyyaml -pytest (for unit tests) +pyserial +textfsm #### Supports: @@ -37,33 +38,38 @@ Alcatel AOS6/AOS8 Avaya ERS Avaya VSP Brocade VDX -Brocade ICX/FastIron Brocade MLX/NetIron +Calix B6 Cisco WLC -Dell-Force10 DNOS9 +Dell-Force10 Dell PowerConnect Huawei Mellanox +NetApp cDOT Palo Alto PAN-OS Pluribus -Ubiquiti EdgeSwitch +Ruckus ICX/FastIron +Ubiquiti EdgeSwitch Vyatta VyOS ###### Experimental A10 Accedian -Alcatel-Lucent SR-OS Aruba Ciena SAOS Cisco Telepresence -CheckPoint Gaia +Check Point GAiA +Coriant +Eltex Enterasys Extreme EXOS Extreme Wing F5 LTM Fortinet MRV Communications OptiSwitch +Nokia/Alcatel SR-OS +QuantaMesh ## Tutorials: diff --git a/netmiko/__init__.py b/netmiko/__init__.py index e538b9be8..e3fa88cd4 100644 --- a/netmiko/__init__.py +++ b/netmiko/__init__.py @@ -20,12 +20,13 @@ # Alternate naming NetmikoTimeoutError = NetMikoTimeoutException NetmikoAuthError = NetMikoAuthenticationException +Netmiko = ConnectHandler -__version__ = '2.0.1' +__version__ = '2.0.2' __all__ = ('ConnectHandler', 'ssh_dispatcher', 'platforms', 'SCPConn', 'FileTransfer', 'NetMikoTimeoutException', 'NetMikoAuthenticationException', 'NetmikoTimeoutError', 'NetmikoAuthError', 'InLineTransfer', 'redispatch', - 'SSHDetect', 'BaseConnection') + 'SSHDetect', 'BaseConnection', 'Netmiko') # Cisco cntl-shift-six sequence CNTL_SHIFT_6 = chr(30) diff --git a/netmiko/a10/a10_ssh.py b/netmiko/a10/a10_ssh.py index a9bb43f48..dd9e752a0 100644 --- a/netmiko/a10/a10_ssh.py +++ b/netmiko/a10/a10_ssh.py @@ -19,3 +19,7 @@ def session_preparation(self): # Clear the read buffer time.sleep(.3 * self.global_delay_factor) self.clear_buffer() + + def save_config(self, cmd='', confirm=True, confirm_response=''): + """Not Implemented""" + raise NotImplementedError diff --git a/netmiko/accedian/accedian_ssh.py b/netmiko/accedian/accedian_ssh.py index 9d742601e..1ffabfa42 100644 --- a/netmiko/accedian/accedian_ssh.py +++ b/netmiko/accedian/accedian_ssh.py @@ -39,3 +39,7 @@ def set_base_prompt(self, pri_prompt_terminator=':', alt_prompt_terminator='#', alt_prompt_terminator=alt_prompt_terminator, delay_factor=delay_factor) return self.base_prompt + + def save_config(self, cmd='', confirm=True, confirm_response=''): + """Not Implemented""" + raise NotImplementedError diff --git a/netmiko/alcatel/alcatel_aos_ssh.py b/netmiko/alcatel/alcatel_aos_ssh.py index 4c9432f83..9c2c58c1f 100644 --- a/netmiko/alcatel/alcatel_aos_ssh.py +++ b/netmiko/alcatel/alcatel_aos_ssh.py @@ -38,3 +38,7 @@ def config_mode(self, *args, **kwargs): def exit_config_mode(self, *args, **kwargs): """No config mode on AOS""" return '' + + def save_config(self, cmd='write memory flash-synchro', confirm=False): + """Save Config""" + return super(AlcatelAosSSH, self).save_config(cmd=cmd, confirm=confirm) diff --git a/netmiko/alcatel/alcatel_sros_ssh.py b/netmiko/alcatel/alcatel_sros_ssh.py index 0fcf40d91..21ec9cf0b 100644 --- a/netmiko/alcatel/alcatel_sros_ssh.py +++ b/netmiko/alcatel/alcatel_sros_ssh.py @@ -42,3 +42,7 @@ def check_config_mode(self, check_string='config', pattern='#'): """ Checks if the device is in configuration mode or not. """ return super(AlcatelSrosSSH, self).check_config_mode(check_string=check_string, pattern=pattern) + + def save_config(self, cmd='', confirm=True, confirm_response=''): + """Not Implemented""" + raise NotImplementedError diff --git a/netmiko/avaya/avaya_ers_ssh.py b/netmiko/avaya/avaya_ers_ssh.py index 281a8c029..f234b78dc 100644 --- a/netmiko/avaya/avaya_ers_ssh.py +++ b/netmiko/avaya/avaya_ers_ssh.py @@ -36,3 +36,7 @@ def special_login_handler(self, delay_factor=1): self.write_channel(self.RETURN) time.sleep(1 * delay_factor) i += 1 + + def save_config(self, cmd='save config', confirm=False): + """Save Config""" + return super(AvayaErsSSH, self).save_config(cmd=cmd, confirm=confirm) diff --git a/netmiko/avaya/avaya_vsp_ssh.py b/netmiko/avaya/avaya_vsp_ssh.py index fd68f940c..a5cade6c2 100644 --- a/netmiko/avaya/avaya_vsp_ssh.py +++ b/netmiko/avaya/avaya_vsp_ssh.py @@ -15,3 +15,7 @@ def session_preparation(self): # Clear the read buffer time.sleep(.3 * self.global_delay_factor) self.clear_buffer() + + def save_config(self, cmd='save config', confirm=False): + """Save Config""" + return super(AvayaVspSSH, self).save_config(cmd=cmd, confirm=confirm) diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index 6c392cd51..33c62b15b 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -1219,6 +1219,10 @@ def commit(self): """Commit method for platforms that support this.""" raise AttributeError("Network device does not support 'commit()' method") + def save_config(self, cmd='', confirm=True, confirm_response=''): + """Not Implemented""" + raise NotImplementedError + class TelnetConnection(BaseConnection): pass diff --git a/netmiko/brocade/brocade_netiron.py b/netmiko/brocade/brocade_netiron.py index 22e795f86..153879cdb 100644 --- a/netmiko/brocade/brocade_netiron.py +++ b/netmiko/brocade/brocade_netiron.py @@ -3,7 +3,9 @@ class BrocadeNetironBase(CiscoSSHConnection): - pass + def save_config(self, cmd='write memory', confirm=False): + """Save Config""" + return super(BrocadeNetironBase, self).save_config(cmd=cmd, confirm=confirm) class BrocadeNetironSSH(BrocadeNetironBase): diff --git a/netmiko/brocade/brocade_nos_ssh.py b/netmiko/brocade/brocade_nos_ssh.py index 70a45842d..4eb315b31 100644 --- a/netmiko/brocade/brocade_nos_ssh.py +++ b/netmiko/brocade/brocade_nos_ssh.py @@ -19,3 +19,9 @@ def special_login_handler(self, delay_factor=1): delay_factor = self.select_delay_factor(delay_factor) self.write_channel(self.RETURN) time.sleep(1 * delay_factor) + + def save_config(self, cmd='copy running-config startup-config', confirm=True, + confirm_response='y'): + """Save Config for Brocade VDX.""" + return super(BrocadeNosSSH, self).save_config(cmd=cmd, confirm=confirm, + confirm_response=confirm_response) diff --git a/netmiko/calix/calix_b6_ssh.py b/netmiko/calix/calix_b6_ssh.py index f55228972..da000c8b7 100644 --- a/netmiko/calix/calix_b6_ssh.py +++ b/netmiko/calix/calix_b6_ssh.py @@ -88,3 +88,7 @@ def exit_config_mode(self, exit_config=None, pattern=''): if exit_config is None: exit_config = 'exit' + self.RETURN + 'end' return super(CalixB6SSH, self).exit_config_mode(exit_config=exit_config, pattern=pattern) + + def save_config(self, cmd='', confirm=True, confirm_response=''): + """Not Implemented""" + raise NotImplementedError diff --git a/netmiko/checkpoint/checkpoint_gaia_ssh.py b/netmiko/checkpoint/checkpoint_gaia_ssh.py index 9c19b47a1..48b5fdca6 100644 --- a/netmiko/checkpoint/checkpoint_gaia_ssh.py +++ b/netmiko/checkpoint/checkpoint_gaia_ssh.py @@ -28,3 +28,7 @@ def config_mode(self, config_command=''): def exit_config_mode(self, exit_config=''): """No config mode for Check Point devices.""" return '' + + def save_config(self, cmd='', confirm=True, confirm_response=''): + """Not Implemented""" + raise NotImplementedError diff --git a/netmiko/ciena/ciena_saos_ssh.py b/netmiko/ciena/ciena_saos_ssh.py index 06b91a4fe..6d04fd20c 100644 --- a/netmiko/ciena/ciena_saos_ssh.py +++ b/netmiko/ciena/ciena_saos_ssh.py @@ -17,3 +17,7 @@ def session_preparation(self): def enable(self, *args, **kwargs): pass + + def save_config(self, cmd='', confirm=True, confirm_response=''): + """Not Implemented""" + raise NotImplementedError diff --git a/netmiko/cisco/cisco_asa_ssh.py b/netmiko/cisco/cisco_asa_ssh.py index f0df10c6c..ce46bf6c2 100644 --- a/netmiko/cisco/cisco_asa_ssh.py +++ b/netmiko/cisco/cisco_asa_ssh.py @@ -103,6 +103,10 @@ def asa_login(self): self.write_channel("login" + self.RETURN) i += 1 + def save_config(self, cmd='write mem', confirm=False): + """Saves Config""" + return super(CiscoAsaSSH, self).save_config(cmd=cmd, confirm=confirm) + class CiscoAsaFileTransfer(CiscoFileTransfer): """Cisco ASA SCP File Transfer driver.""" diff --git a/netmiko/cisco/cisco_ios.py b/netmiko/cisco/cisco_ios.py index b774269a7..34d30c615 100644 --- a/netmiko/cisco/cisco_ios.py +++ b/netmiko/cisco/cisco_ios.py @@ -21,6 +21,10 @@ def session_preparation(self): time.sleep(.3 * self.global_delay_factor) self.clear_buffer() + def save_config(self, cmd='write mem', confirm=False): + """Saves Config Using Copy Run Start""" + return super(CiscoIosBase, self).save_config(cmd=cmd, confirm=confirm) + class CiscoIosSSH(CiscoIosBase): """Cisco IOS SSH driver.""" diff --git a/netmiko/cisco/cisco_nxos_ssh.py b/netmiko/cisco/cisco_nxos_ssh.py index f4effc620..0e75a7ff6 100644 --- a/netmiko/cisco/cisco_nxos_ssh.py +++ b/netmiko/cisco/cisco_nxos_ssh.py @@ -47,18 +47,6 @@ def __init__(self, ssh_conn, source_file, dest_file, file_system='bootflash:', d else: raise ValueError("Invalid direction specified") - def remote_space_available(self, search_pattern=r"(\d+) bytes free"): - """Return space available on remote device.""" - return super(CiscoNxosFileTransfer, self).remote_space_available( - search_pattern=search_pattern - ) - - def verify_space_available(self, search_pattern=r"(\d+) bytes free"): - """Verify sufficient space is available on destination file system (return boolean).""" - return super(CiscoNxosFileTransfer, self).verify_space_available( - search_pattern=search_pattern - ) - def check_file_exists(self, remote_cmd=""): """Check if the dest_file already exists on the file system (return boolean).""" raise NotImplementedError diff --git a/netmiko/cisco/cisco_s300.py b/netmiko/cisco/cisco_s300.py index 8d31e717c..f183f8a3b 100644 --- a/netmiko/cisco/cisco_s300.py +++ b/netmiko/cisco/cisco_s300.py @@ -21,4 +21,6 @@ def session_preparation(self): self.set_terminal_width(command='terminal width 511') # Clear the read buffer time.sleep(.3 * self.global_delay_factor) - self.clear_buffer() + + def save_config(self, cmd='write memory', confirm=True, confirm_response='Y'): + return super(CiscoS300SSH, self).save_config(cmd=cmd, confirm=confirm) diff --git a/netmiko/cisco/cisco_tp_tcce.py b/netmiko/cisco/cisco_tp_tcce.py index 0bd20714d..e779dac50 100644 --- a/netmiko/cisco/cisco_tp_tcce.py +++ b/netmiko/cisco/cisco_tp_tcce.py @@ -85,3 +85,7 @@ def send_command(self, *args, **kwargs): output = super(CiscoSSHConnection, self).send_command(*args, **kwargs) return output + + def save_config(self): + """Not Implemented""" + raise NotImplementedError diff --git a/netmiko/cisco/cisco_wlc_ssh.py b/netmiko/cisco/cisco_wlc_ssh.py index 95d65e54b..2e65241bd 100644 --- a/netmiko/cisco/cisco_wlc_ssh.py +++ b/netmiko/cisco/cisco_wlc_ssh.py @@ -158,3 +158,7 @@ def send_config_set(self, config_commands=None, exit_config_mode=True, delay_fac output = self._sanitize_output(output) 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) diff --git a/netmiko/cisco/cisco_xr_ssh.py b/netmiko/cisco/cisco_xr_ssh.py index 4cce75fdf..2c2df3ba5 100644 --- a/netmiko/cisco/cisco_xr_ssh.py +++ b/netmiko/cisco/cisco_xr_ssh.py @@ -125,3 +125,7 @@ def exit_config_mode(self, exit_config='end'): if self.check_config_mode(): raise ValueError("Failed to exit configuration mode") return output + + def save_config(self): + """Not Implemented (use commit() method)""" + raise NotImplementedError diff --git a/netmiko/cisco_base_connection.py b/netmiko/cisco_base_connection.py index 567774d41..31ad1eb53 100644 --- a/netmiko/cisco_base_connection.py +++ b/netmiko/cisco_base_connection.py @@ -63,7 +63,8 @@ def serial_login(self, pri_prompt_terminator=r'#\s*$', alt_prompt_terminator=r'> 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)", pwd_pattern=r"assword", + username_pattern=r"(?:[Uu]ser:|sername|ogin|User Name)", + pwd_pattern=r"assword", delay_factor=1, max_loops=20): """Telnet login. Can be username/password or just password.""" delay_factor = self.select_delay_factor(delay_factor) @@ -162,6 +163,22 @@ def _autodetect_fs(self, cmd='dir', pattern=r'Directory of (.*)/'): raise ValueError("An error occurred in dynamically determining remote file " "system: {} {}".format(cmd, output)) + def save_config(self, cmd='copy running-config startup-config', confirm=False, + confirm_response=''): + """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 + class CiscoSSHConnection(CiscoBaseConnection): pass diff --git a/netmiko/coriant/coriant_ssh.py b/netmiko/coriant/coriant_ssh.py index 11b602cd4..9fa901960 100644 --- a/netmiko/coriant/coriant_ssh.py +++ b/netmiko/coriant/coriant_ssh.py @@ -36,3 +36,7 @@ def set_base_prompt(self, pri_prompt_terminator=':', alt_prompt_terminator='>', alt_prompt_terminator=alt_prompt_terminator, delay_factor=delay_factor) return self.base_prompt + + def save_config(self, cmd='', confirm=True, confirm_response=''): + """Not Implemented""" + raise NotImplementedError diff --git a/netmiko/dell/dell_force10_ssh.py b/netmiko/dell/dell_force10_ssh.py index 3646908fe..0481b9ab1 100644 --- a/netmiko/dell/dell_force10_ssh.py +++ b/netmiko/dell/dell_force10_ssh.py @@ -5,4 +5,7 @@ class DellForce10SSH(CiscoSSHConnection): """Dell Force10 Driver - supports DNOS9.""" - pass + + def save_config(self, cmd='copy running-configuration startup-configuration', confirm=False): + """Saves Config""" + return super(DellForce10SSH, self).save_config(cmd=cmd, confirm=confirm) diff --git a/netmiko/dell/dell_powerconnect.py b/netmiko/dell/dell_powerconnect.py index a464e3d3d..f85ea22a4 100644 --- a/netmiko/dell/dell_powerconnect.py +++ b/netmiko/dell/dell_powerconnect.py @@ -27,21 +27,21 @@ def session_preparation(self): def set_base_prompt(self, pri_prompt_terminator='>', alt_prompt_terminator='#', delay_factor=1): """Sets self.base_prompt: used as delimiter for stripping of trailing prompt in output.""" - prompt = super(DellPowerConnectSSH, self).set_base_prompt( - pri_prompt_terminator=pri_prompt_terminator, - alt_prompt_terminator=alt_prompt_terminator, - delay_factor=delay_factor) + prompt = super(DellPowerConnectBase, self).set_base_prompt( + pri_prompt_terminator=pri_prompt_terminator, + alt_prompt_terminator=alt_prompt_terminator, + delay_factor=delay_factor) prompt = prompt.strip() self.base_prompt = prompt return self.base_prompt def check_config_mode(self, check_string='(config)#'): """Checks if the device is in configuration mode""" - return super(DellPowerConnectSSH, self).check_config_mode(check_string=check_string) + return super(DellPowerConnectBase, self).check_config_mode(check_string=check_string) def config_mode(self, config_command='config'): """Enter configuration mode.""" - return super(DellPowerConnectSSH, self).config_mode(config_command=config_command) + return super(DellPowerConnectBase, self).config_mode(config_command=config_command) class DellPowerConnectSSH(DellPowerConnectBase): diff --git a/netmiko/eltex/eltex_ssh.py b/netmiko/eltex/eltex_ssh.py index d6bc47964..8c3b1beff 100644 --- a/netmiko/eltex/eltex_ssh.py +++ b/netmiko/eltex/eltex_ssh.py @@ -15,3 +15,7 @@ def session_preparation(self): # Clear the read buffer time.sleep(.3 * self.global_delay_factor) self.clear_buffer() + + def save_config(self, cmd='', confirm=True, confirm_response=''): + """Not Implemented""" + raise NotImplementedError diff --git a/netmiko/enterasys/enterasys_ssh.py b/netmiko/enterasys/enterasys_ssh.py index 69e0d42a9..9a1b2c83b 100644 --- a/netmiko/enterasys/enterasys_ssh.py +++ b/netmiko/enterasys/enterasys_ssh.py @@ -14,3 +14,7 @@ def session_preparation(self): # Clear the read buffer time.sleep(.3 * self.global_delay_factor) self.clear_buffer() + + def save_config(self, cmd='', confirm=True, confirm_response=''): + """Not Implemented""" + raise NotImplementedError diff --git a/netmiko/extreme/__init__.py b/netmiko/extreme/__init__.py index 150eff194..1074d9428 100644 --- a/netmiko/extreme/__init__.py +++ b/netmiko/extreme/__init__.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals -from netmiko.extreme.extreme_ssh import ExtremeSSH +from netmiko.extreme.extreme_exos import ExtremeSSH +from netmiko.extreme.extreme_exos import ExtremeTelnet from netmiko.extreme.extreme_wing_ssh import ExtremeWingSSH -__all__ = ['ExtremeSSH', 'ExtremeWingSSH'] +__all__ = ['ExtremeSSH', 'ExtremeWingSSH', 'ExtremeTelnet'] diff --git a/netmiko/extreme/extreme_ssh.py b/netmiko/extreme/extreme_exos.py similarity index 69% rename from netmiko/extreme/extreme_ssh.py rename to netmiko/extreme/extreme_exos.py index 01a09cfb5..94e406a81 100644 --- a/netmiko/extreme/extreme_ssh.py +++ b/netmiko/extreme/extreme_exos.py @@ -5,7 +5,7 @@ from netmiko.cisco_base_connection import CiscoSSHConnection -class ExtremeSSH(CiscoSSHConnection): +class ExtremeBase(CiscoSSHConnection): """Extreme support. Designed for EXOS >= 15.0 @@ -14,6 +14,7 @@ def session_preparation(self): self._test_channel_read() self.set_base_prompt() self.disable_paging(command="disable clipaging") + self.send_command_timing("disable cli prompting") # Clear the read buffer time.sleep(.3 * self.global_delay_factor) self.clear_buffer() @@ -33,7 +34,7 @@ def set_base_prompt(self, *args, **kwargs): * testhost.4 # * testhost.5 # """ - cur_base_prompt = super(ExtremeSSH, self).set_base_prompt(*args, **kwargs) + cur_base_prompt = super(ExtremeBase, self).set_base_prompt(*args, **kwargs) # Strip off any leading * or whitespace chars; strip off trailing period and digits match = re.search(r'[\*\s]*(.*)\.\d+', cur_base_prompt) if match: @@ -50,7 +51,7 @@ def send_command(self, *args, **kwargs): # refresh self.base_prompt self.set_base_prompt() - return super(ExtremeSSH, self).send_command(*args, **kwargs) + return super(ExtremeBase, self).send_command(*args, **kwargs) def config_mode(self, config_command=''): """No configuration mode on Extreme.""" @@ -58,8 +59,23 @@ def config_mode(self, config_command=''): def check_config_mode(self, check_string='#'): """Checks whether in configuration mode. Returns a boolean.""" - return super(ExtremeSSH, self).check_config_mode(check_string=check_string) + return super(ExtremeBase, self).check_config_mode(check_string=check_string) def exit_config_mode(self, exit_config=''): """No configuration mode on Extreme.""" return '' + + def save_config(self, cmd='save configuration primary', confirm=False): + """Saves configuration.""" + return super(ExtremeBase, self).save_config(cmd=cmd, confirm=confirm) + + +class ExtremeSSH(ExtremeBase): + pass + + +class ExtremeTelnet(ExtremeBase): + 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(ExtremeTelnet, self).__init__(*args, **kwargs) diff --git a/netmiko/fortinet/fortinet_ssh.py b/netmiko/fortinet/fortinet_ssh.py index 79c0f8405..793d28311 100644 --- a/netmiko/fortinet/fortinet_ssh.py +++ b/netmiko/fortinet/fortinet_ssh.py @@ -68,3 +68,7 @@ def config_mode(self, config_command=''): def exit_config_mode(self, exit_config=''): """No config mode for Fortinet devices.""" return '' + + def save_config(self, cmd='', confirm=True, confirm_response=''): + """Not Implemented""" + raise NotImplementedError diff --git a/netmiko/hp/hp_comware_ssh.py b/netmiko/hp/hp_comware_ssh.py index b115bb1c1..76d867084 100644 --- a/netmiko/hp/hp_comware_ssh.py +++ b/netmiko/hp/hp_comware_ssh.py @@ -76,3 +76,7 @@ def exit_enable_mode(self, exit_command='return'): def check_enable_mode(self, check_string=']'): """enable mode on Comware is system-view.""" return self.check_config_mode(check_string=check_string) + + def save_config(self, cmd='save force', confirm=False): + """Save Config.""" + return super(HPComwareSSH, self).save_config(cmd=cmd, confirm=confirm) diff --git a/netmiko/hp/hp_procurve_ssh.py b/netmiko/hp/hp_procurve_ssh.py index d92367914..fadaa6a59 100644 --- a/netmiko/hp/hp_procurve_ssh.py +++ b/netmiko/hp/hp_procurve_ssh.py @@ -72,3 +72,7 @@ def cleanup(self): except socket.error: break count += 1 + + def save_config(self, cmd='write memory', confirm=False): + """Save Config.""" + return super(HPProcurveSSH, self).save_config(cmd=cmd, confirm=confirm) diff --git a/netmiko/huawei/__init__.py b/netmiko/huawei/__init__.py index 5e219b01f..9a9a938e4 100644 --- a/netmiko/huawei/__init__.py +++ b/netmiko/huawei/__init__.py @@ -1,4 +1,4 @@ from __future__ import unicode_literals -from netmiko.huawei.huawei_ssh import HuaweiSSH +from netmiko.huawei.huawei_ssh import HuaweiSSH, HuaweiVrpv8SSH -__all__ = ['HuaweiSSH'] +__all__ = ['HuaweiSSH', 'HuaweiVrpv8SSH'] diff --git a/netmiko/huawei/huawei_ssh.py b/netmiko/huawei/huawei_ssh.py index 67474afc7..2c584a762 100644 --- a/netmiko/huawei/huawei_ssh.py +++ b/netmiko/huawei/huawei_ssh.py @@ -80,3 +80,43 @@ def set_base_prompt(self, pri_prompt_terminator='>', alt_prompt_terminator=']', log.debug("prompt: {0}".format(self.base_prompt)) return self.base_prompt + + def save_config(self, cmd='save', confirm=False, confirm_response=''): + """ Save Config for HuaweiSSH""" + return super(HuaweiSSH, self).save_config(cmd=cmd, confirm=confirm) + + +class HuaweiVrpv8SSH(HuaweiSSH): + + def commit(self, comment='', delay_factor=1): + """ + Commit the candidate configuration. + + Commit the entered configuration. Raise an error and return the failure + if the commit fails. + + default: + command_string = commit + comment: + command_string = commit comment + + """ + delay_factor = self.select_delay_factor(delay_factor) + error_marker = 'Failed to generate committed config' + command_string = 'commit' + + if comment: + command_string += ' comment "{}"'.format(comment) + + output = self.config_mode() + output += self.send_command_expect(command_string, strip_prompt=False, + strip_command=False, delay_factor=delay_factor) + output += self.exit_config_mode() + + if error_marker in output: + raise ValueError('Commit failed with following errors:\n\n{}'.format(output)) + return output + + def save_config(self, cmd='', confirm=True, confirm_response=''): + """Not Implemented""" + raise NotImplementedError diff --git a/netmiko/linux/linux_ssh.py b/netmiko/linux/linux_ssh.py index 9f34bcfe0..4f51110c0 100644 --- a/netmiko/linux/linux_ssh.py +++ b/netmiko/linux/linux_ssh.py @@ -85,3 +85,7 @@ def enable(self, cmd='sudo su', pattern='ssword', re_flags=re.IGNORECASE): def cleanup(self): """Try to Gracefully exit the SSH session.""" self.write_channel("exit" + self.RETURN) + + def save_config(self, cmd='', confirm=True, confirm_response=''): + """Not Implemented""" + raise NotImplementedError diff --git a/netmiko/mellanox/mellanox_ssh.py b/netmiko/mellanox/mellanox_ssh.py index a817b2f45..3f9aa5f59 100644 --- a/netmiko/mellanox/mellanox_ssh.py +++ b/netmiko/mellanox/mellanox_ssh.py @@ -47,3 +47,12 @@ def exit_config_mode(self, exit_config='exit', pattern='#'): raise ValueError("Failed to exit configuration mode") log.debug("exit_config_mode: {0}".format(output)) return output + + def save_config(self, cmd='configuration write', confirm=False, + confirm_response=''): + """Save Config on Mellanox devices Enters and Leaves Config Mode""" + output = self.enable() + output += self.config_mode() + output += self.send_command(cmd) + output += self.exit_config_mode() + return output diff --git a/netmiko/mrv/mrv_ssh.py b/netmiko/mrv/mrv_ssh.py index ed72174ff..dddbe7412 100644 --- a/netmiko/mrv/mrv_ssh.py +++ b/netmiko/mrv/mrv_ssh.py @@ -29,3 +29,7 @@ def enable(self, cmd='enable', pattern=r'#', re_flags=re.IGNORECASE): "the 'secret' argument to ConnectHandler." raise ValueError(msg) return output + + def save_config(self, cmd='save config flash', confirm=False): + """Saves configuration.""" + return super(MrvOptiswitchSSH, self).save_config(cmd=cmd, confirm=confirm) diff --git a/netmiko/quanta/quanta_mesh_ssh.py b/netmiko/quanta/quanta_mesh_ssh.py index c1b64cae6..ddca0961e 100644 --- a/netmiko/quanta/quanta_mesh_ssh.py +++ b/netmiko/quanta/quanta_mesh_ssh.py @@ -10,3 +10,7 @@ def disable_paging(self, command="no pager", delay_factor=1): def config_mode(self, config_command='configure'): """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 diff --git a/netmiko/ruckus/ruckus_fastiron.py b/netmiko/ruckus/ruckus_fastiron.py index 377028bb7..79819c689 100644 --- a/netmiko/ruckus/ruckus_fastiron.py +++ b/netmiko/ruckus/ruckus_fastiron.py @@ -48,6 +48,10 @@ def enable(self, cmd='enable', pattern=r'(ssword|User Name)', re_flags=re.IGNORE "the 'secret' argument to ConnectHandler." raise ValueError(msg) + def save_config(self, cmd='write mem', confirm=False): + """Saves configuration.""" + return super(RuckusFastironBase, self).save_config(cmd=cmd, confirm=confirm) + class RuckusFastironTelnet(RuckusFastironBase): def __init__(self, *args, **kwargs): diff --git a/netmiko/scp_handler.py b/netmiko/scp_handler.py index 962a2016f..5e6c2ca53 100644 --- a/netmiko/scp_handler.py +++ b/netmiko/scp_handler.py @@ -91,7 +91,7 @@ def close_scp_chan(self): self.scp_conn.close() self.scp_conn = None - def remote_space_available(self, search_pattern=r"bytes total \((.*) bytes free\)"): + def remote_space_available(self, search_pattern=r"(\d+) bytes free"): """Return space available on remote device.""" remote_cmd = "dir {}".format(self.file_system) remote_output = self.ssh_ctl_chan.send_command_expect(remote_cmd) @@ -103,7 +103,7 @@ def local_space_available(self): destination_stats = os.statvfs(".") return destination_stats.f_bsize * destination_stats.f_bavail - def verify_space_available(self, search_pattern=r"bytes total \((.*) bytes free\)"): + def verify_space_available(self, search_pattern=r"(\d+) bytes free"): """Verify sufficient space is available on destination file system (return boolean).""" if self.direction == 'put': space_avail = self.remote_space_available(search_pattern=search_pattern) diff --git a/netmiko/ssh_autodetect.py b/netmiko/ssh_autodetect.py index 88c909342..89d0a4cf4 100644 --- a/netmiko/ssh_autodetect.py +++ b/netmiko/ssh_autodetect.py @@ -48,6 +48,7 @@ # 'dispatch' key is the SSHDetect method to call. dispatch key will be popped off dictionary # remaining keys indicate kwargs that will be passed to dispatch method. +# Note, the 'cmd' needs to avoid output paging. SSH_MAPPER_BASE = { 'alcatel_aos': { "cmd": "show system", @@ -56,8 +57,11 @@ "dispatch": "_autodetect_std", }, 'alcatel_sros': { - "cmd": "show version | match ALCATEL", - "search_patterns": ["TiMOS"], + "cmd": "show version | match TiMOS", + "search_patterns": [ + "Nokia", + "Alcatel", + ], "priority": 99, "dispatch": "_autodetect_std", }, @@ -96,13 +100,26 @@ }, 'huawei': { "cmd": "display version | inc Huawei", - "search_patterns": ["Huawei Technologies", "Huawei Versatile Routing Platform Software"], + "search_patterns": [ + "Huawei Technologies", + "Huawei Versatile Routing Platform Software" + ], "priority": 99, "dispatch": "_autodetect_std", }, 'juniper_junos': { "cmd": "show version | match JUNOS", - "search_patterns": ["JUNOS Software Release", "JUNOS .+ Software"], + "search_patterns": [ + "JUNOS Software Release", + "JUNOS .+ Software", + "JUNOS OS Kernel", + ], + "priority": 99, + "dispatch": "_autodetect_std", + }, + 'dell_force10': { + "cmd": "show version | grep Type", + "search_patterns": ["S4048-ON"], "priority": 99, "dispatch": "_autodetect_std", }, diff --git a/netmiko/ssh_dispatcher.py b/netmiko/ssh_dispatcher.py index ee130914b..de3b5b58e 100644 --- a/netmiko/ssh_dispatcher.py +++ b/netmiko/ssh_dispatcher.py @@ -5,7 +5,8 @@ from netmiko.accedian import AccedianSSH from netmiko.alcatel import AlcatelAosSSH from netmiko.alcatel import AlcatelSrosSSH -from netmiko.arista import AristaSSH, AristaFileTransfer +from netmiko.arista import AristaSSH +# from netmiko.arista import AristaFileTransfer from netmiko.aruba import ArubaSSH from netmiko.avaya import AvayaErsSSH from netmiko.avaya import AvayaVspSSH @@ -30,11 +31,13 @@ from netmiko.enterasys import EnterasysSSH from netmiko.extreme import ExtremeSSH 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.huawei import HuaweiSSH -from netmiko.juniper import JuniperSSH, JuniperFileTransfer +from netmiko.huawei import HuaweiSSH, HuaweiVrpv8SSH +from netmiko.juniper import JuniperSSH +# from netmiko.juniper import JuniperFileTransfer from netmiko.linux import LinuxSSH from netmiko.mellanox import MellanoxSSH from netmiko.mrv import MrvOptiswitchSSH @@ -90,6 +93,7 @@ 'hp_comware': HPComwareSSH, 'hp_procurve': HPProcurveSSH, 'huawei': HuaweiSSH, + 'huawei_vrpv8': HuaweiVrpv8SSH, 'juniper': JuniperSSH, 'juniper_junos': JuniperSSH, 'linux': LinuxSSH, @@ -108,12 +112,12 @@ } FILE_TRANSFER_MAP = { - 'arista_eos': AristaFileTransfer, + # 'arista_eos': AristaFileTransfer, 'cisco_asa': CiscoAsaFileTransfer, 'cisco_ios': CiscoIosFileTransfer, 'cisco_xe': CiscoIosFileTransfer, 'cisco_nxos': CiscoNxosFileTransfer, - 'juniper_junos': JuniperFileTransfer, + # 'juniper_junos': JuniperFileTransfer, } # Also support keys that end in _ssh @@ -137,6 +141,7 @@ CLASS_MAPPER['cisco_ios_telnet'] = CiscoIosTelnet CLASS_MAPPER['dell_powerconnect_telnet'] = DellPowerConnectTelnet CLASS_MAPPER['generic_termserver_telnet'] = TerminalServerTelnet +CLASS_MAPPER['extreme_telnet'] = ExtremeTelnet CLASS_MAPPER['ruckus_fastiron_telnet'] = RuckusFastironTelnet # Add serial drivers diff --git a/netmiko/ubiquiti/edge_ssh.py b/netmiko/ubiquiti/edge_ssh.py index 9bc275a17..b4cf5631c 100644 --- a/netmiko/ubiquiti/edge_ssh.py +++ b/netmiko/ubiquiti/edge_ssh.py @@ -25,3 +25,7 @@ def exit_config_mode(self, exit_config='exit'): def exit_enable_mode(self, exit_command='exit'): """Exit enable mode.""" return super(UbiquitiEdgeSSH, self).exit_enable_mode(exit_command=exit_command) + + def save_config(self, cmd='write memory', confirm=False): + """Saves configuration.""" + return super(UbiquitiEdgeSSH, self).save_config(cmd=cmd, confirm=confirm) diff --git a/netmiko/vyos/vyos_ssh.py b/netmiko/vyos/vyos_ssh.py index ce8807362..f44b6e265 100644 --- a/netmiko/vyos/vyos_ssh.py +++ b/netmiko/vyos/vyos_ssh.py @@ -96,3 +96,7 @@ def send_config_set(self, config_commands=None, exit_config_mode=False, delay_fa strip_prompt=strip_prompt, strip_command=strip_command, config_mode_command=config_mode_command) + + def save_config(self, cmd='', confirm=True, confirm_response=''): + """Not Implemented""" + raise NotImplementedError