diff --git a/docs/changelog/2024/april.rst b/docs/changelog/2024/april.rst new file mode 100644 index 00000000..d00f23e6 --- /dev/null +++ b/docs/changelog/2024/april.rst @@ -0,0 +1,102 @@ +April 2024 +========== + + - Unicon v24.4 +------------------------ + + + +.. csv-table:: Module Versions + :header: "Modules", "Versions" + + ``unicon.plugins``, v24.4 + ``unicon``, v24.4 + +Install Instructions +^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: bash + + bash$ pip install unicon.plugins + bash$ pip install unicon + +Upgrade Instructions +^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: bash + + bash$ pip install --upgrade unicon.plugins + bash$ pip install --upgrade unicon + +Features and Bug Fixes: +^^^^^^^^^^^^^^^^^^^^^^^ + + + + +Changelogs +^^^^^^^^^^ +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* sshutils + * add_tunnel + * add logic to handle allocating ports based on the tunnel type. + +* unicon + * Bases/Routers + * Do learn hostname if only the learn pattern is in the statmachine patterns. + * Update the connection init logic. + * Patterns + * Add Bad secrets to bad_passwords pattern. + +* unicon/bases + * Router/connection_provider + * Update logic to not learn the hostname when the device is in shell mode. + + +-------------------------------------------------------------------------------- + New +-------------------------------------------------------------------------------- + +* unicon + * Connection provider + * Add args and kwargs for connect function + + +-------------------------------------------------------------------------------- + New +-------------------------------------------------------------------------------- + +* iosxe + * statemachine + * add pki_hexmode state for iosxe + +* iosxr + * Added get_commit_cmd + * Added support for 'commit best-effort' command. + +* stackresetstandbyrp + * Added iosxe/stack StackResetStandbyRP + * iosxe/stack service reset_standby_rp + * Check whole stack readiness to decide the result of reset_standby_rp + + +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* iosxr/spitfire + * Modified Prompt Recovery Commands + * Updated prompt recovery commands to user CTRL+C. + +* iosxe + * connection provider + * Get the pattern for the enable statment from state machine for handeling device prompts after + +* resetstandbyrp + * Modified generic ResetStandbyRP + * Fixed to handle the optinal argument "reply" + + diff --git a/docs/changelog/index.rst b/docs/changelog/index.rst index 34daa759..6267ffc7 100644 --- a/docs/changelog/index.rst +++ b/docs/changelog/index.rst @@ -4,6 +4,7 @@ Changelog .. toctree:: :maxdepth: 2 + 2024/april 2024/march 2024/february 2024/january diff --git a/docs/changelog_plugins/2024/april.rst b/docs/changelog_plugins/2024/april.rst new file mode 100644 index 00000000..2ef86bdf --- /dev/null +++ b/docs/changelog_plugins/2024/april.rst @@ -0,0 +1,67 @@ +April 2024 +========== + + - Unicon.Plugins v24.4 +------------------------ + + + +.. csv-table:: Module Versions + :header: "Modules", "Versions" + + ``unicon.plugins``, v24.4 + ``unicon``, v24.4 + +Install Instructions +^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: bash + + bash$ pip install unicon.plugins + bash$ pip install unicon + +Upgrade Instructions +^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: bash + + bash$ pip install --upgrade unicon.plugins + bash$ pip install --upgrade unicon + +Features and Bug Fixes: +^^^^^^^^^^^^^^^^^^^^^^^ + + + + +Changelogs +^^^^^^^^^^ +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* generic + * Use stricter pattern for enable password + * Update standby locked pattern + * Add connection closed statement to execute service + * Add standby locked state to single RP statemachine + * Update escape character handler timing settings + * Revert adding connection closed statement to execute service + * Update config transition logic + * Add `result_check_per_command` option to disable/enable error checking per configuration command + +* iosxe + * Fix operating mode logic + * More prompt handling updated + * Added statements to token discovery dialog + +* iosxr + * Add standby locked state to single RP statemachine + * Change default behavior of ``configure()`` service, error check after all commands by default + * Add handler for `show configuration failed` errors to ``configure()`` service. + * Add `SHOW_CONFIG_FAILED_CMD` setting for command to use, default `show configuration failed` + +* other + * update pid token list + + diff --git a/docs/changelog_plugins/index.rst b/docs/changelog_plugins/index.rst index ef7fb36b..04171b38 100644 --- a/docs/changelog_plugins/index.rst +++ b/docs/changelog_plugins/index.rst @@ -4,6 +4,7 @@ Plugins Changelog .. toctree:: :maxdepth: 2 + 2024/april 2024/march 2024/february 2024/january diff --git a/src/unicon/plugins/__init__.py b/src/unicon/plugins/__init__.py index 3d6c9e28..3f8de17c 100644 --- a/src/unicon/plugins/__init__.py +++ b/src/unicon/plugins/__init__.py @@ -1,4 +1,4 @@ -__version__ = '24.3' +__version__ = '24.4' supported_chassis = [ 'single_rp', diff --git a/src/unicon/plugins/cheetah/ap/__init__.py b/src/unicon/plugins/cheetah/ap/__init__.py index 684a4ca5..f96c3b3e 100644 --- a/src/unicon/plugins/cheetah/ap/__init__.py +++ b/src/unicon/plugins/cheetah/ap/__init__.py @@ -54,4 +54,4 @@ class ApSingleRpConnection(BaseSingleRpConnection): state_machine_class = ApSingleRpStateMachine connection_provider_class = ApSingleRpConnectionProvider subcommand_list = ApServiceList - settings = ApSettings() + settings = ApSettings() \ No newline at end of file diff --git a/src/unicon/plugins/cheetah/ap/patterns.py b/src/unicon/plugins/cheetah/ap/patterns.py index 4bc8fb5c..89d05fb4 100644 --- a/src/unicon/plugins/cheetah/ap/patterns.py +++ b/src/unicon/plugins/cheetah/ap/patterns.py @@ -9,4 +9,4 @@ class CheetahAPPatterns(GenericPatterns): def __init__(self): super().__init__() - self.ap_shell_prompt = r'^(.*?)\w+:\/(.*?)#\s?$' + self.ap_shell_prompt = r'^(.*?)\w+:\/(.*?)#\s?$' \ No newline at end of file diff --git a/src/unicon/plugins/cheetah/ap/service_implementation.py b/src/unicon/plugins/cheetah/ap/service_implementation.py index 10ea74eb..56c0d67f 100644 --- a/src/unicon/plugins/cheetah/ap/service_implementation.py +++ b/src/unicon/plugins/cheetah/ap/service_implementation.py @@ -21,4 +21,4 @@ def call_service(self, command=None, reply=Dialog([]), timeout=None, *args, class Reload(GenericReload): def __init__(self, connection, context, **kwargs): super().__init__(connection, context, **kwargs) - self.dialog = self.dialog + Dialog(ap_reload_list) + self.dialog = self.dialog + Dialog(ap_reload_list) \ No newline at end of file diff --git a/src/unicon/plugins/cheetah/ap/service_patterns.py b/src/unicon/plugins/cheetah/ap/service_patterns.py index e26a172a..05d4f343 100644 --- a/src/unicon/plugins/cheetah/ap/service_patterns.py +++ b/src/unicon/plugins/cheetah/ap/service_patterns.py @@ -5,5 +5,4 @@ class APReloadPatterns(ReloadPatterns): def __init__(self): super().__init__() - self.ap_shell_prompt = r'^Proceed with reload (command (\W+cold\W)?)?(\?) (\[)+confirm+(\])$' - \ No newline at end of file + self.ap_shell_prompt = r'^Proceed with reload (command (\W+cold\W)?)?(\?) (\[)+confirm+(\])$' \ No newline at end of file diff --git a/src/unicon/plugins/cheetah/ap/service_statement.py b/src/unicon/plugins/cheetah/ap/service_statement.py index 970c19d9..ab271cde 100644 --- a/src/unicon/plugins/cheetah/ap/service_statement.py +++ b/src/unicon/plugins/cheetah/ap/service_statement.py @@ -1,10 +1,8 @@ """ Module: unicon.plugins.generic - Authors: pyATS TEAM (pyats-support@cisco.com, pyats-support-ext@cisco.com) - Description: Module for defining all Services Statement, handlers(callback) and Statement list for service dialog would be defined here. @@ -34,5 +32,4 @@ def send_response(spawn, response=""): continue_timer=False) ap_reload_list = list(reload_statement_list) -ap_reload_list.insert(0,ap_shell_prompt) - +ap_reload_list.insert(0,ap_shell_prompt) \ No newline at end of file diff --git a/src/unicon/plugins/cheetah/ap/settings.py b/src/unicon/plugins/cheetah/ap/settings.py index bccb3d66..6ef4faf2 100644 --- a/src/unicon/plugins/cheetah/ap/settings.py +++ b/src/unicon/plugins/cheetah/ap/settings.py @@ -13,4 +13,4 @@ def __init__(self): 'terminal width 0', 'show version', 'logging console disable', - ] + ] \ No newline at end of file diff --git a/src/unicon/plugins/cheetah/ap/statemachine.py b/src/unicon/plugins/cheetah/ap/statemachine.py index b35b6547..881b9d2d 100644 --- a/src/unicon/plugins/cheetah/ap/statemachine.py +++ b/src/unicon/plugins/cheetah/ap/statemachine.py @@ -47,4 +47,4 @@ def create(self): self.add_path(shell_to_enable) self.add_path(enable_to_disable) - self.add_default_statements(default_statement_list) \ No newline at end of file + self.add_default_statements(default_statement_list) diff --git a/src/unicon/plugins/generic/patterns.py b/src/unicon/plugins/generic/patterns.py index 4a46ae0f..3e358255 100644 --- a/src/unicon/plugins/generic/patterns.py +++ b/src/unicon/plugins/generic/patterns.py @@ -36,7 +36,7 @@ def __init__(self): self.rommon_prompt = r'^(.*?)(rommon[\s\d]*>|switch:)\s?$' # self.standby_enable_prompt = r'^(.*?)(RouterRP-standby|%N-standby|%N-sdby|%N\(standby\))#\s?$' # self.standby_disable_prompt = r'^(.*?)(RouterRP-standby|%N-standby|%N-sdby|%N\(standby\))>\s?$' - self.standby_locked = r'[S|s]tandby console disabled' + self.standby_locked = r'^.*?([S|s]tandby console disabled|This \(D\)RP Node is not ready or active for login \/configuration.*)' self.shell_prompt = r'^(.*)%N\(shell\)>\s?' self.disconnect_message = r'Received disconnect from .*:' @@ -56,7 +56,7 @@ def __init__(self): self.passphrase_prompt = r'^.*Enter passphrase for key .*?:\s*?' - self.learn_os_prompt = r'^(.*?([>\$~%]|[^#\s]#|~ #|~/|^admin:|^#)\s?(\x1b\S+)?)$|(^.*This \(D\)RP Node is not ready or active for login \/configuration.*)' + self.learn_os_prompt = r'^(.*?([>\$~%]|[^#\s]#|~ #|~/|^admin:|^#)\s?(\x1b\S+)?)$' self.sudo_password_prompt = r'^.*(\[sudo\] password for .*?:|This is your UNIX password:)\s*$' @@ -74,6 +74,7 @@ def __init__(self): self.config_start = r'Enter configuration commands, one per line\.\s+End with CNTL/Z\.\s*$' self.enable_secret = r'^.*?(Enter|Confirm) enable secret:\s*$' + self.enable_password = r'^.*?enable[\r\n]*.*?[Pp]assword( for )?(\S+)?: ?$' self.enter_your_selection_2 = r'^.*?Enter your selection( \[2])?:\s*$' diff --git a/src/unicon/plugins/generic/service_implementation.py b/src/unicon/plugins/generic/service_implementation.py index 81fcfdb2..bbf7a005 100644 --- a/src/unicon/plugins/generic/service_implementation.py +++ b/src/unicon/plugins/generic/service_implementation.py @@ -811,6 +811,8 @@ class Configure(BaseService): 0 means to send all commands in a single chunk bulk_chunk_sleep: sleep between sending command chunks, default is 0.5 sec + result_check_per_command: boolean option, check results after + each command (default: True) Returns: command output on Success, raise SubCommandFailure on failure @@ -835,6 +837,7 @@ def __init__(self, connection, context, **kwargs): self.bulk_chunk_lines = connection.settings.BULK_CONFIG_CHUNK_LINES self.bulk_chunk_sleep = connection.settings.BULK_CONFIG_CHUNK_SLEEP self.valid_transition_commands = ['end', 'exit'] + self.valid_transition_states = ['config_pki_hexmode'] self.state_change_matched_retries = connection.settings.EXECUTE_STATE_CHANGE_MATCH_RETRIES self.state_change_matched_retry_sleep = connection.settings.EXECUTE_STATE_CHANGE_MATCH_RETRY_SLEEP self.__dict__.update(kwargs) @@ -877,9 +880,11 @@ def call_service(self, # noqa: C901 bulk=None, bulk_chunk_lines=None, bulk_chunk_sleep=None, + result_check_per_command=True, *args, **kwargs): + self.result_check_per_command = result_check_per_command con = self.connection sm = self.get_sm() handle = self.get_handle(target) @@ -910,7 +915,9 @@ def call_service(self, # noqa: C901 def config_state_change(spawn, from_state, sm): last_cmd = spawn.last_sent.strip() - if last_cmd not in self.valid_transition_commands: + # check if the last command is not in the list of valid commands and the state is not in the list of valid states + # for transition + if last_cmd not in self.valid_transition_commands and from_state.name not in self.valid_transition_states: invalid_state_change_action( spawn, err_state=from_state, sm=sm) else: @@ -1018,14 +1025,15 @@ def process_dialog_on_handle(self, handle, dialog, timeout): hostname=handle.hostname, result_match=cmd_result) self.result += cmd_result - try: - self.get_service_result() - except SubCommandFailure: - # Go to end state after command failure, - handle.state_machine.go_to(self.end_state, - handle.spawn, - context=self.context) - raise + if self.result_check_per_command: + try: + self.get_service_result() + except SubCommandFailure: + # Go to end state after command failure, + handle.state_machine.go_to(self.end_state, + handle.spawn, + context=self.context) + raise def update_hostname_if_needed(self, cmd_list): for cmd in cmd_list: @@ -2471,7 +2479,8 @@ def call_service(self, command='redundancy reload peer', # noqa: C901 raise SubCommandFailure("Standby found but not in the expected state") dialog = self.service_dialog(handle=con.active, - service_dialog=self.dialog) + service_dialog=self.dialog+reply) + # Issue standby reset command con.active.spawn.sendline(command) try: diff --git a/src/unicon/plugins/generic/settings.py b/src/unicon/plugins/generic/settings.py index a9510740..f5bf7781 100644 --- a/src/unicon/plugins/generic/settings.py +++ b/src/unicon/plugins/generic/settings.py @@ -86,13 +86,13 @@ def __init__(self): # When connecting to a device via telnet, how long (in seconds) # to pause before checking the spawn buffer - self.ESCAPE_CHAR_CHATTY_TERM_WAIT = 0.25 + self.ESCAPE_CHAR_CHATTY_TERM_WAIT = 0.5 # number of cycles to wait for if the terminal is still chatty self.ESCAPE_CHAR_CHATTY_TERM_WAIT_RETRIES = 12 # prompt wait delay - self.ESCAPE_CHAR_PROMPT_WAIT = 0.5 + self.ESCAPE_CHAR_PROMPT_WAIT = 1 # prompt wait retries # (wait time: 0.5, 1, 1.5, 2, 2.5, 3, 3.5 == total wait: 14.0s) diff --git a/src/unicon/plugins/generic/statemachine.py b/src/unicon/plugins/generic/statemachine.py index 508f5de8..2099c95f 100644 --- a/src/unicon/plugins/generic/statemachine.py +++ b/src/unicon/plugins/generic/statemachine.py @@ -15,7 +15,7 @@ import re from time import sleep -from unicon.core.errors import StateMachineError +from unicon.core.errors import StateMachineError, TimeoutError as UniconTimeoutError from unicon.plugins.generic.statements import GenericStatements from unicon.plugins.generic.patterns import GenericPatterns @@ -68,7 +68,10 @@ def config_transition(statemachine, spawn, context): for attempt in range(max_attempts + 1): spawn.sendline(statemachine.config_command) - dialog.process(spawn, timeout=spawn.settings.CONFIG_TIMEOUT, context=context) + try: + dialog.process(spawn, timeout=spawn.settings.CONFIG_TIMEOUT, context=context) + except UniconTimeoutError: + pass statemachine.detect_state(spawn) if statemachine.current_state == 'config': @@ -78,6 +81,11 @@ def config_transition(statemachine, spawn, context): spawn.log.warning('*** Could not enter config mode, waiting {} seconds. Retry attempt {}/{} ***'.format( wait_time, attempt + 1, max_attempts)) sleep(wait_time) + spawn.sendline() + statemachine.go_to('any', spawn) + if statemachine.current_state == 'config': + spawn.sendline() + return raise StateMachineError('Unable to transition to config mode') @@ -116,7 +124,8 @@ def create(self): enable_to_rommon = Path(enable, rommon, 'reload', None) enable_to_config = Path(enable, config, config_transition, Dialog([statements.syslog_msg_stmt])) disable_to_enable = Path(disable, enable, 'enable', - Dialog([statements.enable_password_stmt, + Dialog([statements.password_stmt, + statements.enable_password_stmt, statements.bad_password_stmt, statements.syslog_stripper_stmt])) config_to_enable = Path(config, enable, 'end', Dialog([statements.syslog_msg_stmt])) @@ -136,6 +145,10 @@ def create(self): self.add_path(enable_to_disable) self.add_default_statements(default_statement_list) + standby_locked = State('standby_locked', patterns.standby_locked) + + self.add_state(standby_locked) + def learn_os_state(self): learn_os = State('learn_os', patterns.learn_os_prompt) self.add_state(learn_os) @@ -157,6 +170,3 @@ def create(self): ########################################################## # State Definition ########################################################## - standby_locked = State('standby_locked', patterns.standby_locked) - - self.add_state(standby_locked) diff --git a/src/unicon/plugins/generic/statements.py b/src/unicon/plugins/generic/statements.py index 2a11dd4a..36494018 100644 --- a/src/unicon/plugins/generic/statements.py +++ b/src/unicon/plugins/generic/statements.py @@ -349,6 +349,9 @@ def ssh_tacacs_handler(spawn, context): def password_handler(spawn, context, session): """ handles password prompt """ + if 'enable' in spawn.last_sent: + return enable_password_handler(spawn, context, session) + credential = get_current_credential(context=context, session=session) if credential: common_cred_password_handler( @@ -478,7 +481,12 @@ def wait_and_enter(spawn, wait=0.5): def more_prompt_handler(spawn): output = utils.remove_backspace(spawn.match.match_output) all_more = re.findall(spawn.settings.MORE_REPLACE_PATTERN, output) - spawn.match.match_output = ''.join(output.rsplit(all_more[-1], 1)) + if all_more: + spawn.match.match_output = ''.join(output.rsplit(all_more[-1], 1)) + spawn.buffer = ''.join(spawn.buffer.rsplit(all_more[-1], 1)) + else: + spawn.match.match_output = output + spawn.buffer = utils.remove_backspace(spawn.buffer) spawn.send(spawn.settings.MORE_CONTINUE) @@ -602,7 +610,7 @@ def __init__(self): args=None, loop_continue=True, continue_timer=False) - self.enable_password_stmt = Statement(pattern=pat.password, + self.enable_password_stmt = Statement(pattern=pat.enable_password, action=enable_password_handler, args=None, loop_continue=True, @@ -621,7 +629,8 @@ def __init__(self): action=more_prompt_handler, args=None, loop_continue=True, - continue_timer=False) + continue_timer=False, + trim_buffer=False) self.confirm_prompt_stmt = Statement(pattern=pat.confirm_prompt, action=sendline, args=None, diff --git a/src/unicon/plugins/iosxe/c9800/ewc_ap/__init__.py b/src/unicon/plugins/iosxe/c9800/ewc_ap/__init__.py deleted file mode 100644 index 1fe959cf..00000000 --- a/src/unicon/plugins/iosxe/c9800/ewc_ap/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ - -from unicon.plugins.iosxe.c9800 import IosXEc9800ServiceList, IosXEc9800SingleRpConnection - -from . import service_implementation as svc -from .statemachine import IosXEEwcSingleRpStateMachine - -class IosXEEwcServiceList(IosXEc9800ServiceList): - def __init__(self): - super().__init__() - self.bash_console = svc.IosXEEWCBashService - self.ap_shell = svc.EWCApShellService - - -class IosXEEwcSingleRpConnection(IosXEc9800SingleRpConnection): - os = 'iosxe' - platform = 'c9800' - model = 'ewc_ap' - chassis_type = 'single_rp' - subcommand_list = IosXEEwcServiceList - state_machine_class = IosXEEwcSingleRpStateMachine diff --git a/src/unicon/plugins/iosxe/cat9k/c9100ap/__init__.py b/src/unicon/plugins/iosxe/cat9k/c9100ap/__init__.py new file mode 100644 index 00000000..b5a44803 --- /dev/null +++ b/src/unicon/plugins/iosxe/cat9k/c9100ap/__init__.py @@ -0,0 +1,37 @@ +from unicon.bases.routers.connection import BaseSingleRpConnection +from unicon.plugins.generic.statemachine import GenericSingleRpStateMachine +from unicon.plugins.generic import ServiceList +from unicon.plugins.generic import GenericSingleRpConnectionProvider +from unicon.plugins.generic import service_implementation as gsvc + +from unicon.plugins.iosxe.cat9k.c9800 import IosXEc9800ServiceList, IosXEc9800SingleRpConnection + +from .settings import ApSettings +from . import service_implementation as svc +from .statemachine import IosXEEwcSingleRpStateMachine + + +class ApServiceList(ServiceList): + def __init__(self): + super().__init__() + self.execute = svc.Execute + self.send = gsvc.Send + self.sendline = gsvc.Sendline + self.expect = gsvc.Expect + self.enable = gsvc.Enable + self.disable = gsvc.Disable + self.reload = gsvc.Reload + self.log_user = gsvc.LogUser + self.bash_console = svc.IosXEEWCBashService + self.ap_shell = svc.EWCApShellService + + +class IosXEEwcSingleRpConnection(IosXEc9800SingleRpConnection): + os = 'iosxe' + platform = 'cat9k' + model = 'c9100ap' + chassis_type = 'single_rp' + subcommand_list = ApServiceList + state_machine_class = IosXEEwcSingleRpStateMachine + subcommand_list = ApServiceList + settings = ApSettings() diff --git a/src/unicon/plugins/iosxe/c9800/ewc_ap/patterns.py b/src/unicon/plugins/iosxe/cat9k/c9100ap/patterns.py similarity index 100% rename from src/unicon/plugins/iosxe/c9800/ewc_ap/patterns.py rename to src/unicon/plugins/iosxe/cat9k/c9100ap/patterns.py diff --git a/src/unicon/plugins/iosxe/c9800/ewc_ap/service_implementation.py b/src/unicon/plugins/iosxe/cat9k/c9100ap/service_implementation.py similarity index 87% rename from src/unicon/plugins/iosxe/c9800/ewc_ap/service_implementation.py rename to src/unicon/plugins/iosxe/cat9k/c9100ap/service_implementation.py index e80afddb..a03e98e0 100644 --- a/src/unicon/plugins/iosxe/c9800/ewc_ap/service_implementation.py +++ b/src/unicon/plugins/iosxe/cat9k/c9100ap/service_implementation.py @@ -7,6 +7,11 @@ from unicon.bases.routers.services import BaseService from unicon.eal.dialogs import Dialog from unicon.plugins.iosxe.service_implementation import BashService as IosXEBashService +from unicon.plugins.generic.service_implementation import \ + Execute as GenericExecute +from unicon.eal.dialogs import Dialog +from unicon.plugins.iosxe.service_statements import confirm + from .patterns import IosXEEWCBashShellPatterns, IosXEEWCAPShellPatterns from .service_statements import enter_bash_shell_statement_list from .settings import IosXEEWCBashShellSettings, IosXEEWCAPShellSettings @@ -18,6 +23,15 @@ bash_shell_patterns = IosXEEWCBashShellPatterns() +class Execute(GenericExecute): + def call_service(self, command=None, reply=Dialog([]), timeout=None, *args, + **kwargs): + command = list() if command is None else command + super().call_service(command, + reply=reply + Dialog([confirm,]), + timeout=timeout, *args, **kwargs) + + class IosXEEWCBashService(IosXEBashService): def pre_service(self, *args, **kwargs): diff --git a/src/unicon/plugins/iosxe/c9800/ewc_ap/service_statements.py b/src/unicon/plugins/iosxe/cat9k/c9100ap/service_statements.py similarity index 100% rename from src/unicon/plugins/iosxe/c9800/ewc_ap/service_statements.py rename to src/unicon/plugins/iosxe/cat9k/c9100ap/service_statements.py diff --git a/src/unicon/plugins/iosxe/c9800/ewc_ap/settings.py b/src/unicon/plugins/iosxe/cat9k/c9100ap/settings.py similarity index 71% rename from src/unicon/plugins/iosxe/c9800/ewc_ap/settings.py rename to src/unicon/plugins/iosxe/cat9k/c9100ap/settings.py index b19bb1ef..d6fb8ba0 100644 --- a/src/unicon/plugins/iosxe/c9800/ewc_ap/settings.py +++ b/src/unicon/plugins/iosxe/cat9k/c9100ap/settings.py @@ -4,8 +4,21 @@ All rights reserved. """ -from unicon.plugins.cheetah.ap.settings import ApSettings from unicon.plugins.iosxe.settings import IosXESettings +from unicon.plugins.generic.settings import GenericSettings + + +class ApSettings(GenericSettings): + def __init__(self): + super().__init__() + + self.HA_INIT_EXEC_COMMANDS = [ + 'exec-timeout 0', + 'terminal length 0', + 'terminal width 0', + 'show version', + 'logging console disable', + ] # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++# diff --git a/src/unicon/plugins/iosxe/c9800/ewc_ap/statemachine.py b/src/unicon/plugins/iosxe/cat9k/c9100ap/statemachine.py similarity index 98% rename from src/unicon/plugins/iosxe/c9800/ewc_ap/statemachine.py rename to src/unicon/plugins/iosxe/cat9k/c9100ap/statemachine.py index d8049c40..20c7575f 100644 --- a/src/unicon/plugins/iosxe/c9800/ewc_ap/statemachine.py +++ b/src/unicon/plugins/iosxe/cat9k/c9100ap/statemachine.py @@ -1,7 +1,7 @@ import re -from unicon.plugins.iosxe.c9800.statemachine import IosXEc9800SingleRpStateMachine +from unicon.plugins.iosxe.cat9k.c9800.statemachine import IosXEc9800SingleRpStateMachine from unicon.statemachine import State, Path from unicon.eal.dialogs import Dialog, Statement from unicon.utils import AttributeDict diff --git a/src/unicon/plugins/iosxe/c9800/__init__.py b/src/unicon/plugins/iosxe/cat9k/c9800/__init__.py similarity index 84% rename from src/unicon/plugins/iosxe/c9800/__init__.py rename to src/unicon/plugins/iosxe/cat9k/c9800/__init__.py index 882d205b..6b6bb875 100644 --- a/src/unicon/plugins/iosxe/c9800/__init__.py +++ b/src/unicon/plugins/iosxe/cat9k/c9800/__init__.py @@ -7,7 +7,7 @@ from .statemachine import IosXEc9800SingleRpStateMachine from .settings import IosXEc9800Settings -from ..cat9k import service_implementation as svc +from .. import service_implementation as svc class IosXEc9800ServiceList(IosXEServiceList): @@ -18,12 +18,14 @@ def __init__(self): class IosXEc9800SingleRpConnection(IosXESingleRpConnection): - platform = 'c9800' + platform = 'cat9k' + model = 'c9800' state_machine_class = IosXEc9800SingleRpStateMachine subcommand_list = IosXEc9800ServiceList settings = IosXEc9800Settings() class IosXEc9800DualRPConnection(IosXEDualRPConnection): - platform = 'c9800' + platform = 'cat9k' + model = 'c9800' settings = IosXEc9800Settings() diff --git a/src/unicon/plugins/iosxe/c9800/settings.py b/src/unicon/plugins/iosxe/cat9k/c9800/settings.py similarity index 100% rename from src/unicon/plugins/iosxe/c9800/settings.py rename to src/unicon/plugins/iosxe/cat9k/c9800/settings.py diff --git a/src/unicon/plugins/iosxe/c9800/statemachine.py b/src/unicon/plugins/iosxe/cat9k/c9800/statemachine.py similarity index 100% rename from src/unicon/plugins/iosxe/c9800/statemachine.py rename to src/unicon/plugins/iosxe/cat9k/c9800/statemachine.py diff --git a/src/unicon/plugins/iosxe/c9800/c9800_cl/__init__.py b/src/unicon/plugins/iosxe/cat9k/c9800cl/__init__.py similarity index 70% rename from src/unicon/plugins/iosxe/c9800/c9800_cl/__init__.py rename to src/unicon/plugins/iosxe/cat9k/c9800cl/__init__.py index d5d5c312..c3c76477 100644 --- a/src/unicon/plugins/iosxe/c9800/c9800_cl/__init__.py +++ b/src/unicon/plugins/iosxe/cat9k/c9800cl/__init__.py @@ -1,5 +1,5 @@ -from unicon.plugins.iosxe.c9800 import IosXEc9800ServiceList, IosXEc9800SingleRpConnection, IosXEc9800DualRPConnection +from unicon.plugins.iosxe.cat9k.c9800 import IosXEc9800ServiceList, IosXEc9800SingleRpConnection, IosXEc9800DualRPConnection class IosXEc9800CLServiceList(IosXEc9800ServiceList): @@ -10,13 +10,13 @@ def __init__(self): class IosXEc9800CLSingleRpConnection(IosXEc9800SingleRpConnection): os = 'iosxe' - platform = 'c9800' + platform = 'cat9k' model = 'c9800_cl' subcommand_list = IosXEc9800CLServiceList class IosXEc9800CLDualRpConnection(IosXEc9800DualRPConnection): os = 'iosxe' - platform = 'c9800' + platform = 'cat9k' model = 'c9800_cl' subcommand_list = IosXEc9800CLServiceList diff --git a/src/unicon/plugins/iosxe/connection_provider.py b/src/unicon/plugins/iosxe/connection_provider.py index 44a3e581..d9df63af 100644 --- a/src/unicon/plugins/iosxe/connection_provider.py +++ b/src/unicon/plugins/iosxe/connection_provider.py @@ -51,7 +51,7 @@ def learn_tokens(self): con.spawn.sendline('pnpa service discovery stop') # The device may reload after the command we get the dialog statements from reload service and try to handle that dialog = con.reload.dialog - dialog.append(Statement(pattern=GenericPatterns().enable_prompt,action=None, + dialog.append(Statement(con.state_machine.get_state('enable').pattern, action=None, args=None, loop_continue=False, continue_timer=False)) dialog.process(con.spawn, context=con.context, @@ -66,5 +66,5 @@ def learn_tokens(self): con.spawn, context=con.context, prompt_recovery=con.prompt_recovery) - super().learn_tokens() + super().learn_tokens() diff --git a/src/unicon/plugins/iosxe/patterns.py b/src/unicon/plugins/iosxe/patterns.py index 410fda80..ea092c24 100644 --- a/src/unicon/plugins/iosxe/patterns.py +++ b/src/unicon/plugins/iosxe/patterns.py @@ -29,7 +29,10 @@ def __init__(self): self.maintenance_mode_prompt = \ r'^(.*?)(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?\(maint-mode\)#[\s\x07]*$' self.press_enter = ReloadPatterns().press_enter - self.config_prompt = r'^(.*)\(.*(con|cfg|ipsec-profile|ca-trustpoint|cs-server|ca-profile|gkm-local-server|cloud|host-list|config-gkm-group|gkm-sa-ipsec|gdoi-coop-ks-config|wsma|enforce-rule)\S*\)#\s?$' + self.config_prompt = r'^(.*)\((?!.*pki-hexmode).*(con|cfg|ipsec-profile|ca-trustpoint|cs-server|ca-profile|gkm-local-server|cloud|host-list|config-gkm-group|gkm-sa-ipsec|gdoi-coop-ks-config|wsma|enforce-rule)\S*\)#\s?$' + + + self.config_pki_prompt = r'^(.*)\(config-pki-hexmode\)#\s?$' self.are_you_sure_ywtdt = r'Are you sure you want to do this\? \[yes/no\]:\s*$' self.do_you_want_to = r'^.*Do you want to remove the above files\? \[y\/n]\s*$' self.confirm_uncommited_changes = r'Uncommitted changes found, commit them\? \[yes\/no\/CANCEL\]\s*$' diff --git a/src/unicon/plugins/iosxe/stack/__init__.py b/src/unicon/plugins/iosxe/stack/__init__.py index c46604de..48d55347 100644 --- a/src/unicon/plugins/iosxe/stack/__init__.py +++ b/src/unicon/plugins/iosxe/stack/__init__.py @@ -8,7 +8,7 @@ from .settings import IosXEStackSettings from .statemachine import StackIosXEStateMachine from .connection_provider import StackRpConnectionProvider -from .service_implementation import StackGetRPState, StackSwitchover, StackReload, StackRommon, StackEnable +from .service_implementation import StackGetRPState, StackSwitchover, StackReload, StackRommon, StackEnable, StackResetStandbyRP class StackIosXEServiceList(HAServiceList): def __init__(self): @@ -22,6 +22,7 @@ def __init__(self): self.get_rp_state = StackGetRPState self.rommon = StackRommon self.enable = StackEnable + self.reset_standby_rp = StackResetStandbyRP class IosXEStackRPConnection(BaseStackRpConnection): diff --git a/src/unicon/plugins/iosxe/stack/service_implementation.py b/src/unicon/plugins/iosxe/stack/service_implementation.py index 6b6720ec..9b347d1f 100644 --- a/src/unicon/plugins/iosxe/stack/service_implementation.py +++ b/src/unicon/plugins/iosxe/stack/service_implementation.py @@ -9,6 +9,7 @@ from .utils import StackUtils from unicon.plugins.generic.statements import custom_auth_statements, buffer_settled +from unicon.plugins.generic.service_statements import standby_reset_rp_statement_list from .service_statements import (switch_prompt, stack_reload_stmt_list, stack_switchover_stmt_list, stack_factory_reset_stmt_list) @@ -458,3 +459,154 @@ def call_service(self, target=None, command='', *args, **kwargs): super().call_service(target=subconn_name, command=command, *args, **kwargs) self.result = True + +class StackResetStandbyRP(BaseService): + """ Service to reset the standby rp. + + Arguments: + + command: command to reset standby, default is"redundancy reload peer" + reply: Dialog which include list of Statements for + additional dialogs prompted by standby reset command, + in-case it is not in the current list. + timeout: Timeout value in sec, Default Value is 500 sec + delay_before_check: Delay in secs before checking stack readiness. Default is 20 secs + + Returns: + True on Success, raise SubCommandFailure on failure. + + Example: + .. code-block:: python + + rtr.reset_standby_rp() + # If command is other than 'redundancy reload peer' + rtr.reset_standby_rp(command="command which will reset standby rp", + timeout=600) + + """ + + def __init__(self, connection, context, **kwargs): + super().__init__(connection, context, **kwargs) + self.start_state = 'enable' + self.end_state = 'enable' + self.timeout = connection.settings.HA_RELOAD_TIMEOUT + self.dialog = Dialog(standby_reset_rp_statement_list) + self.__dict__.update(kwargs) + + def pre_service(self, *args, **kwargs): + self.prompt_recovery = kwargs.get('prompt_recovery', False) + if self.connection.is_connected: + return + elif self.connection.reconnect: + self.connection.connect() + else: + raise ConnectionError("Connection is not established to device") + state_machine = self.connection.active.state_machine + state_machine.go_to(self.start_state, + self.connection.active.spawn, + context=self.connection.context) + + def post_service(self, *args, **kwargs): + state_machine = self.connection.active.state_machine + state_machine.go_to(self.end_state, + self.connection.active.spawn, + context=self.connection.context) + + def call_service(self, command='redundancy reload peer', # noqa: C901 + reply=Dialog([]), + timeout=None, + delay_before_check=20, + *args, + **kwargs): + # create an alias for connection. + con = self.connection + timeout = timeout or self.timeout + # resetting the standby rp for + con.log.debug("+++ Issuing reset on %s with " + "reset_command %s and timeout is %s +++" + % (con.hostname, command, timeout)) + + # Check is it possible to reset the standby? + rp_state = con.get_rp_state(target='standby', timeout=100) + + if re.search('DISABLED', rp_state): + raise SubCommandFailure("No Standby found") + + if 'standby_check' in kwargs and not re.search(kwargs['standby_check'], rp_state): + raise SubCommandFailure("Standby found but not in the expected state") + + dialog = self.service_dialog(handle=con.active, + service_dialog=self.dialog+reply) + + # Issue standby reset command + con.active.spawn.sendline(command) + try: + dialog.process(con.active.spawn, + timeout=30, + context=con.active.context) + except TimeoutError: + pass + except SubCommandFailure as err: + raise SubCommandFailure("Failed to reset standby rp %s" % str(err)) from err + + con.log.info(f'Sleep {delay_before_check} seconds before checking standby readiness') + sleep(delay_before_check) + # get current time before checking stack readiness + start_time = time() + if not utils.is_all_member_ready(con, timeout=timeout): + self.result = False + else: + # make sure standby reload/reset has been done + # get current time + end_time = time() + # calculate the time taken to reload the standby + time_taken = end_time - start_time + con.log.info("Time taken to reload Standby RP: %s seconds" % time_taken) + if time_taken < 60: + raise SubCommandFailure("Reload time is too short. Standby RP reload/reset did not happen") + + # check mac address of 0000.0000.0000 which is the temporary value before standby is really ready + con.log.info('Make sure no invalid mac address 0000.0000.0000') + + def _check_invalid_mac(con): + ''' Check if there is any invalid mac address 0000.0000.0000 + Return True if no invalid mac address found + ''' + parsed = utils.get_redundancy_details(con) + for sw in parsed: + if parsed[sw]['mac'] == '0000.0000.0000': + return True + return False + + from genie.utils.timeout import Timeout + exec_timeout = Timeout(timeout, 15) + found_invalid_mac = False + while exec_timeout.iterate(): + con.log.info('Make sure no invalid mac address 0000.0000.0000') + if not _check_invalid_mac(con): + con.log.info('Did not find invalid mac as 0000.0000.0000') + found_invalid_mac = False + break + else: + con.log.warning('Found 0000.0000.0000 mac address') + found_invalid_mac = True + exec_timeout.sleep() + continue + else: + if found_invalid_mac: + raise SubCommandFailure('Found 0000.0000.0000 mac address. Stack is not really ready') + else: + con.log.info('Did not find invalid mac as 0000.0000.0000. Stack is ready') + + con.log.info("Successfully reloaded Standby RP") + con.log.info("Reconnecting to the device, to make sure console is ready") + + try: + con.disconnect() + con.connect() + except Exception as err: + raise SubCommandFailure("Failed to reconnect to the device: %s" % str(err)) from err + + con.log.info("Successfully reloaded Standby RP") + + self.result = True diff --git a/src/unicon/plugins/iosxe/statemachine.py b/src/unicon/plugins/iosxe/statemachine.py index c2b515e5..1bc9569e 100644 --- a/src/unicon/plugins/iosxe/statemachine.py +++ b/src/unicon/plugins/iosxe/statemachine.py @@ -123,8 +123,10 @@ def create(self): tclsh = State('tclsh', patterns.tclsh_prompt) macro = State('macro', patterns.macro_prompt) maintenance = State('maintenance', patterns.maintenance_mode_prompt) + config_pki_hexmode = State('config_pki_hexmode', patterns.config_pki_prompt) disable_to_enable = Path(disable, enable, 'enable', Dialog([ + statements.password_stmt, statements.enable_password_stmt, statements.bad_password_stmt, statements.syslog_stripper_stmt @@ -145,6 +147,8 @@ def create(self): enable_to_maintanance = Path(enable, maintenance, enable_to_maintenance_transition, None) maintenance_to_enable = Path(maintenance, enable, maintenance_to_enable_transition, None) + config_pki_hexmode_to_config = Path(config_pki_hexmode, config, 'quit', None) + self.add_state(disable) self.add_state(enable) self.add_state(config) @@ -152,6 +156,7 @@ def create(self): self.add_state(tclsh) self.add_state(macro) self.add_state(maintenance) + self.add_state(config_pki_hexmode) self.add_path(disable_to_enable) self.add_path(enable_to_disable) @@ -164,6 +169,7 @@ def create(self): self.add_path(macro_to_config) self.add_path(enable_to_maintanance) self.add_path(maintenance_to_enable) + self.add_path(config_pki_hexmode_to_config) enable_to_rommon = Path(enable, rommon, 'reload', Dialog( connection_statement_list + reload_statement_list)) diff --git a/src/unicon/plugins/iosxr/iosxrv/statemachine.py b/src/unicon/plugins/iosxr/iosxrv/statemachine.py index 2de80f1b..a64b42ad 100755 --- a/src/unicon/plugins/iosxr/iosxrv/statemachine.py +++ b/src/unicon/plugins/iosxr/iosxrv/statemachine.py @@ -3,7 +3,7 @@ from unicon.plugins.iosxr.statemachine import IOSXRSingleRpStateMachine from unicon.plugins.iosxr.iosxrv.patterns import IOSXRVPatterns -from unicon.plugins.iosxr.statements import IOSXRStatements +from unicon.plugins.iosxr.statements import IOSXRStatements, handle_failed_config from unicon.statemachine import State, Path from unicon.eal.dialogs import Statement, Dialog @@ -31,8 +31,7 @@ def create(self): config_dialog = Dialog([ [patterns.commit_changes_prompt, 'sendline(yes)', None, True, False], [patterns.commit_replace_prompt, 'sendline(yes)', None, True, False], - [patterns.configuration_failed_message, - self.handle_failed_config, None, True, False] + [patterns.configuration_failed_message, handle_failed_config, None, True, False] ]) enable_to_config = Path(enable, config, 'configure terminal', None) diff --git a/src/unicon/plugins/iosxr/moonshine/statemachine.py b/src/unicon/plugins/iosxr/moonshine/statemachine.py index 48a07979..3e2952e1 100755 --- a/src/unicon/plugins/iosxr/moonshine/statemachine.py +++ b/src/unicon/plugins/iosxr/moonshine/statemachine.py @@ -1,6 +1,7 @@ __author__ = "Isobel Ormiston " from unicon.plugins.iosxr.statemachine import IOSXRSingleRpStateMachine +from unicon.plugins.iosxr.statements import handle_failed_config from unicon.plugins.iosxr.moonshine.patterns import MoonshinePatterns from unicon.plugins.iosxr.moonshine.statements import MoonshineStatements from unicon.statemachine import State, Path @@ -29,8 +30,7 @@ def create(self): config_dialog = Dialog([ [patterns.commit_changes_prompt, 'sendline(yes)', None, True, False], [patterns.commit_replace_prompt, 'sendline(yes)', None, True, False], - [patterns.configuration_failed_message, self.handle_failed_config, - None, True, False] + [patterns.configuration_failed_message, handle_failed_config, None, True, False] ]) shell_to_enable = Path(shell, enable, 'exec', None) diff --git a/src/unicon/plugins/iosxr/service_implementation.py b/src/unicon/plugins/iosxr/service_implementation.py index b5a05968..8c3cef2c 100755 --- a/src/unicon/plugins/iosxr/service_implementation.py +++ b/src/unicon/plugins/iosxr/service_implementation.py @@ -13,7 +13,8 @@ from .service_statements import (switchover_statement_list, config_commit_stmt_list, - execution_statement_list) + execution_statement_list, + configure_statement_list) from .utils import IosxrUtils @@ -24,6 +25,8 @@ def get_commit_cmd(**kwargs): commit_cmd = 'commit force' elif 'replace' in kwargs and kwargs['replace'] is True: commit_cmd = 'commit replace' + elif 'best_effort' in kwargs and kwargs['best_effort'] is True: + commit_cmd = 'commit best-effort' else: commit_cmd = 'commit' return commit_cmd @@ -41,13 +44,16 @@ def __init__(self, connection, context, **kwargs): super().__init__(connection, context, **kwargs) self.start_state = 'config' self.end_state = 'enable' + self.dialog += Dialog(configure_statement_list) def call_service(self, command=[], reply=Dialog([]), timeout=None, *args, **kwargs): self.commit_cmd = get_commit_cmd(**kwargs) super().call_service(command, reply=reply + Dialog(config_commit_stmt_list), - timeout=timeout, *args, **kwargs) + timeout=timeout, + result_check_per_command=False, + *args, **kwargs) class ConfigureExclusive(Configure): diff --git a/src/unicon/plugins/iosxr/service_statements.py b/src/unicon/plugins/iosxr/service_statements.py index 54d63e63..68944372 100644 --- a/src/unicon/plugins/iosxr/service_statements.py +++ b/src/unicon/plugins/iosxr/service_statements.py @@ -3,7 +3,7 @@ from .service_patterns import (IOSXRSwitchoverPatterns, IOSXRReloadPatterns) from unicon.plugins.iosxr.patterns import IOSXRPatterns - +from unicon.plugins.iosxr.statements import handle_failed_config pat = IOSXRSwitchoverPatterns() @@ -39,6 +39,12 @@ loop_continue=True, continue_timer=False) +failed_config_statement = Statement(pattern=pat.configuration_failed_message, + action=handle_failed_config, + args={'abort': False}, + loop_continue=True, + continue_timer=False) + pat = IOSXRReloadPatterns() confirm_module_reload_stmt = Statement(pattern=pat.reload_module_prompt, action='sendline(yes)', @@ -60,3 +66,5 @@ reload_statement_list = [confirm_module_reload_stmt] + +configure_statement_list = [failed_config_statement] diff --git a/src/unicon/plugins/iosxr/settings.py b/src/unicon/plugins/iosxr/settings.py index 3b4d323a..3eb81935 100755 --- a/src/unicon/plugins/iosxr/settings.py +++ b/src/unicon/plugins/iosxr/settings.py @@ -57,3 +57,5 @@ def __init__(self): self.SHOW_REDUNDANCY_CMD = 'show redundancy | inc ^Node' self.REDUNDANCY_STATE_PATTERN = r'^Node \S+ is in (.*?) role' + + self.SHOW_CONFIG_FAILED_CMD = 'show configuration failed' diff --git a/src/unicon/plugins/iosxr/spitfire/settings.py b/src/unicon/plugins/iosxr/spitfire/settings.py index 10c7421e..fa16f04a 100644 --- a/src/unicon/plugins/iosxr/spitfire/settings.py +++ b/src/unicon/plugins/iosxr/spitfire/settings.py @@ -28,3 +28,6 @@ def __init__(self): ] self.CONFIG_TIMEOUT = 600 self.STANDBY_STATE_REGEX = r'Standby node .* is (.*)' + + # Default commands: Enter key , Ctrl-C, Enter Key + self.PROMPT_RECOVERY_COMMANDS = ['\r', '\x03', '\r'] diff --git a/src/unicon/plugins/iosxr/spitfire/statemachine.py b/src/unicon/plugins/iosxr/spitfire/statemachine.py index d12fc98e..52ae8d95 100644 --- a/src/unicon/plugins/iosxr/spitfire/statemachine.py +++ b/src/unicon/plugins/iosxr/spitfire/statemachine.py @@ -1,6 +1,7 @@ __author__ = "Sritej K V R " from unicon.plugins.iosxr.statemachine import IOSXRSingleRpStateMachine +from unicon.plugins.iosxr.statements import handle_failed_config from unicon.plugins.iosxr.spitfire.patterns import SpitfirePatterns from unicon.plugins.iosxr.spitfire.statements import SpitfireStatements from unicon.statemachine import State, Path @@ -69,8 +70,7 @@ def create(self): config_dialog = Dialog([ [patterns.commit_changes_prompt, 'sendline(yes)', None, True, False], [patterns.commit_replace_prompt, 'sendline(yes)', None, True, False], - [patterns.configuration_failed_message, - self.handle_failed_config, None, True, False] + [patterns.configuration_failed_message, handle_failed_config, None, True, False] ]) xr_to_bmc = Path(xr, bmc, switch_console, login_dialog) diff --git a/src/unicon/plugins/iosxr/statemachine.py b/src/unicon/plugins/iosxr/statemachine.py index 78538e68..93fae1fc 100755 --- a/src/unicon/plugins/iosxr/statemachine.py +++ b/src/unicon/plugins/iosxr/statemachine.py @@ -2,7 +2,7 @@ from unicon.statemachine import StateMachine from unicon.plugins.iosxr.patterns import IOSXRPatterns -from unicon.plugins.iosxr.statements import IOSXRStatements +from unicon.plugins.iosxr.statements import IOSXRStatements, handle_failed_config from unicon.plugins.generic.statemachine import config_transition from unicon.statemachine import State, Path from unicon.eal.dialogs import Statement, Dialog @@ -49,8 +49,7 @@ def create(self): config_dialog = Dialog([ [patterns.commit_changes_prompt, 'sendline(yes)', None, True, False], [patterns.commit_replace_prompt, 'sendline(yes)', None, True, False], - [patterns.configuration_failed_message, - self.handle_failed_config, None, True, False] + [patterns.configuration_failed_message, handle_failed_config, None, True, False] ]) enable_to_exclusive = Path(enable, exclusive, 'configure exclusive', None) @@ -81,10 +80,13 @@ def create(self): self.add_default_statements(default_commands) + standby_locked = State('standby_locked', patterns.standby_prompt) + self.add_state(standby_locked) + @staticmethod def handle_failed_config(spawn): spawn.read_update_buffer() - spawn.sendline('show configuration failed') + spawn.sendline(spawn.settings.SHOW_CONFIG_FAILED_CMD) spawn.expect([patterns.config_prompt]) spawn.sendline('abort') @@ -96,6 +98,3 @@ def __init__(self, hostname=None): def create(self): super().create() - - standby_locked = State('standby_locked', patterns.standby_prompt) - self.add_state(standby_locked) diff --git a/src/unicon/plugins/iosxr/statements.py b/src/unicon/plugins/iosxr/statements.py index db3fefa9..ba110d11 100755 --- a/src/unicon/plugins/iosxr/statements.py +++ b/src/unicon/plugins/iosxr/statements.py @@ -11,6 +11,14 @@ patterns = IOSXRPatterns() +def handle_failed_config(spawn, abort=True): + spawn.read_update_buffer() + spawn.sendline('show configuration failed') + if abort: + spawn.expect([patterns.config_prompt]) + spawn.sendline('abort') + + def password_handler(spawn, context, session): """ handles password prompt """ diff --git a/src/unicon/plugins/pid_tokens.csv b/src/unicon/plugins/pid_tokens.csv index 5b108e93..92e4b817 100644 --- a/src/unicon/plugins/pid_tokens.csv +++ b/src/unicon/plugins/pid_tokens.csv @@ -1,12 +1,14 @@ pid,os,platform,model,submodel 2501FRAD-FX,ios,c2k,c2500, 2501LANFRAD-FX,ios,c2k,c2500, -8201,iosxr,c8k,c8200, -8202,iosxr,c8k,c8200, -8804,iosxr,c8k,c8800, -8808,iosxr,c8k,c8800, -8812,iosxr,c8k,c8800, -8818,iosxr,c8k,c8800, +8201,iosxr,spitfire,8200, +8201-32FH,iosxr,spitfire,8200, +8202,iosxr,spitfire,8200, +8202-32FH-M,iosxr,spitfire,8200, +8804,iosxr,spitfire,8800, +8808,iosxr,spitfire,8800, +8812,iosxr,spitfire,8800, +8818,iosxr,spitfire,8800, ASR-9001,iosxr,asr9k,asr9000, ASR-9001-S,iosxr,asr9k,asr9000, ASR-9006-SYS,iosxr,asr9k,asr9000, @@ -28,6 +30,7 @@ ASR1002,iosxe,asr1k,asr1000, ASR1002-F,iosxe,asr1k,asr1000, ASR1004,iosxe,asr1k,asr1000, ASR1006,iosxe,asr1k,asr1000, +ASR1006-X,iosxe,asr1k,asr1000, ASR1013,iosxe,asr1k,asr1000, C1000-16FP-2G-L,iosxe,cat1k,c1000, C1000-16P-2G-L,iosxe,cat1k,c1000, @@ -58,6 +61,7 @@ C1000FE-24P-4G-L,iosxe,cat1k,c1000, C1000FE-24T-4G-L,iosxe,cat1k,c1000, C1000FE-48P-4G-L,iosxe,cat1k,c1000, C1000FE-48T-4G-L,iosxe,cat1k,c1000, +C1100TG-1N32A,iosxe,isr,c1100, C1101-4P,ios,c1k,c1100, C1101-4PLTEP,ios,c1k,c1100, C1101-4PLTEPWA,ios,c1k,c1100, @@ -805,6 +809,7 @@ NCS-5504,iosxr,ncs5k,ncs5500, NCS-5508,iosxr,ncs5k,ncs5500, NCS-5516,iosxr,ncs5k,ncs5500, NCS-55A1-24Q6H-S,iosxr,ncs5k,ncs5500, +NCS-55A1-36H-SE-S,iosxr,ncs5k,ncs5500, NCS-55A1-48Q6H,iosxr,ncs5k,ncs5500, NCS-6008,iosxr,ncs6k,ncs6000, NCS-F-CHASS,iosxr,ncs6k,ncs6000, @@ -829,6 +834,7 @@ NCS4KF-SA-DC,iosxr,ncs4k,ncs4000, Nexus1000V,nxos,n1k,n1000, Nexus1000Vh,nxos,n1k,n1000, Nexus9000v,nxos,n9k,n9000, +R-IOSXRV9000-CC,iosxr,iosxrv,xrv9000, SPIAD2901-8FXS/K9,ios,c2k,c2900, WS-C1000,iosxe,cat1k,c1000, WS-C1131,iosxe,cat1k,c1100, diff --git a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml index c9e39d81..5358d3b8 100644 --- a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml +++ b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml @@ -441,6 +441,8 @@ general_config: new_state: config_general_server "crypto gkm group g1": new_state: iosxe_config_1 + "crypto pki certificate chain SLA-TrustPoint": + new_state: config-cert-chain "ntp server vrf foo 1.2.3.4": "% IP routing table foo does not exist" "iox": "" "app-hosting appid guestshell": "" @@ -541,6 +543,23 @@ general_config_ca_profile: "end": new_state: general_enable +config-cert-chain: + prompt: "%N(config-cert-chain)#" + commands: + "certificate ca 01": + new_state: + config_pki_hexmode + "end": + new_state: + general_enable + +config_pki_hexmode: + prompt: "%N(config-pki-hexmode)#" + commands: + "quit": + new_state: + config-cert-chain + general_bash: prompt: "[%N_RP_0:/]$" commands: @@ -709,6 +728,16 @@ slow_config_mode: - 0:,4,0 new_state: general_config +slow_config_mode2: + prompt: "%N#" + commands: + "show version | include operating mode": "" + "config term": + response: "" + timing: + - 0:,15,0 + new_state: general_config + config_locked: prompt: "%N#" @@ -1607,3 +1636,10 @@ general_enable_reload_to_rommon: <<: *gen_enable_cmds "reload": new_state: press_return + + +general_enable_no_operating_mode: + prompt: "%N#" + commands: + <<: *gen_enable_cmds + "show version | include operating mode" : "unexpected output" diff --git a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_ewc.yaml b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_ewc.yaml index 9e52f7f3..94fe7ff1 100644 --- a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_ewc.yaml +++ b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data_ewc.yaml @@ -3,6 +3,10 @@ ewc_enable: prompt: "WLC3C57.31C5.7BC8#" commands: + "exec-timeout 0": "" + "terminal length 0": "" + "terminal width 0": "" + "logging console disable": "" "term length 0": "" "term width 0": "" "show version | include operating mode" : "" diff --git a/src/unicon/plugins/tests/mock_data/iosxr/iosxr_mock_data.yaml b/src/unicon/plugins/tests/mock_data/iosxr/iosxr_mock_data.yaml index d6491e3d..1596c7f2 100644 --- a/src/unicon/plugins/tests/mock_data/iosxr/iosxr_mock_data.yaml +++ b/src/unicon/plugins/tests/mock_data/iosxr/iosxr_mock_data.yaml @@ -59,6 +59,41 @@ exec: enable: prompt: "RP/0/RP0/CPU0:%N#" commands: &enable_cmds + "show ipv4 virtual address status": | + Thu Mar 14 21:41:47.627 UTC + VRF Name: default + Virtual IP: 5.2.21.22/16 + Active Interface Name: MgmtEth0/RP0/CPU0/0 + Active Interface MAC Address: 88fc.5dc4.9d90 + + VRF Node Create Timestamp : .7337 + ARP Add Timestamp : Sat Jul 01 2002241054 16:32:19.1471 + RIB Add Timestamp : .7337 + SNMAC Add Timestamp : N/A + + "show route ipv4 next-hop MgmtEth0/RP0/CPU0/0": | + Thu Mar 14 22:20:54.077 UTC + + Codes: C - connected, S - static, R - RIP, B - BGP, (>) - Diversion path + D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area + N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2 + E1 - OSPF external type 1, E2 - OSPF external type 2, E - EGP + i - ISIS, L1 - IS-IS level-1, L2 - IS-IS level-2 + ia - IS-IS inter area, su - IS-IS summary null, * - candidate default + U - per-user static route, o - ODR, L - local, G - DAGR, l - LISP + A - access/subscriber, a - Application route + M - mobile route, r - RPL, t - Traffic Engineering, (!) - FRR Backup path + + Gateway of last resort is not set + + S 5.0.0.0/8 [1/0] via 5.2.0.1, 06:27:16 + C 5.2.0.0/16 is directly connected, 06:27:16, MgmtEth0/RP0/CPU0/0 + L 5.2.21.20/32 is directly connected, 06:27:16, MgmtEth0/RP0/CPU0/0 + L 5.2.21.22/32 [0/0] via 5.2.21.22, 06:27:16, MgmtEth0/RP0/CPU0/0 + S 5.255.253.6/32 [1/0] via 5.2.0.1, 06:27:16 + S 10.0.0.0/8 [1/0] via 5.2.0.1, 06:27:16 + S 223.255.254.254/32 [1/0] via 5.2.0.1, 06:27:16 + "end": new_state: enable "exit": @@ -440,9 +475,14 @@ config: "commit force": "" "commit replace": new_state: commit_replace + "commit best-effort": + new_state: commit_best_effort "test failed": new_state: failed_config + "test failed2": + new_state: + failed_config2 "redundancy": new_state: config_redundancy @@ -529,6 +569,13 @@ commit_replace: "yes": new_state: config +commit_best_effort: + preface: "This commit will replace or remove the entire running configuration. This\noperation can be service affecting.\n" + prompt: "Do you wish to proceed? [no]:" + commands: + "yes": + new_state: config + failed_config: prompt: "RP/0/RP0/CPU0:%N(config)#" commands: @@ -536,18 +583,7 @@ failed_config: "end": response: "Uncommitted changes found, commit them before exiting(yes/no/cancel)? [cancel]:" new_state: failed_config_uncommitted - -failed_config_uncommitted: - prompt: "" - commands: - "yes": - response: "% Failed to commit one or more configuration items during a pseudo-atomic operation. All changes made have been reverted. Please issue 'show configuration failed [inheritance]' from this session to view the errors" - new_state: failed_config_show - -failed_config_show: - prompt: "RP/0/RP0/CPU0:%N(config)#" - commands: - "show configuration failed": |2 + "show configuration failed": &show_conf_failed |2 Fri Aug 3 15:34:40.336 UTC !! SEMANTIC ERRORS: This configuration was rejected by !! the system due to semantic errors. The individual @@ -561,6 +597,27 @@ failed_config_show: ! end +failed_config2: + prompt: "RP/0/RP0/CPU0:%N(config)#" + commands: + "commit": | + % Failed to commit one or more configuration items during a pseudo-atomic operation. All changes made have been reverted. Please issue 'show configuration failed [inheritance]' from this session to view the errors + "end": + response: "Uncommitted changes found, commit them before exiting(yes/no/cancel)? [cancel]:" + new_state: failed_config_uncommitted + "show configuration failed": *show_conf_failed + +failed_config_uncommitted: + prompt: "" + commands: + "yes": + response: "% Failed to commit one or more configuration items during a pseudo-atomic operation. All changes made have been reverted. Please issue 'show configuration failed [inheritance]' from this session to view the errors" + new_state: failed_config_show + +failed_config_show: + prompt: "RP/0/RP0/CPU0:%N(config)#" + commands: + "show configuration failed": *show_conf_failed "abort": new_state: enable diff --git a/src/unicon/plugins/tests/test_plugin_ios.py b/src/unicon/plugins/tests/test_plugin_ios.py index 0efa0601..1dfb19ac 100644 --- a/src/unicon/plugins/tests/test_plugin_ios.py +++ b/src/unicon/plugins/tests/test_plugin_ios.py @@ -76,6 +76,9 @@ def test_connect_mit_check_init_commands(self): c.setup_connection = Mock() c.state_machine = Mock() + mock_state = Mock() + mock_state.pattern = 'Router#' + c.state_machine.get_state = Mock(return_value=mock_state) c.state_machine.states = [] c._get_learned_hostname = Mock(return_value='Router') c.connection_provider = c.connection_provider_class(c) diff --git a/src/unicon/plugins/tests/test_plugin_iosxe.py b/src/unicon/plugins/tests/test_plugin_iosxe.py index 8ee8f97d..47ff6948 100644 --- a/src/unicon/plugins/tests/test_plugin_iosxe.py +++ b/src/unicon/plugins/tests/test_plugin_iosxe.py @@ -33,8 +33,41 @@ def test_asr_login_connect(self): os='iosxe', credentials=dict(default=dict(username='cisco', password='cisco')), log_buffer=True) - c.connect() - self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') + try: + c.connect() + self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') + finally: + c.disconnect() + + def test_isr_controller_mode_connect(self): + testbed = ''' + devices: + Router: + type: router + os: iosxe + platform: isr + credentials: + default: + username: cisco + password: cisco + connections: + defaults: + class: 'unicon.Unicon' + cli: + command: mock_device_cli --os iosxe --state isr_exec_1 --hostname Router + ''' + t = loader.load(testbed) + d = t.devices.Router + try: + d.connect() + finally: + d.disconnect() + self.assertEqual(d.os, 'iosxe') + self.assertEqual(d.version, '17.14') + self.assertEqual(d.platform, 'sdwan') + self.assertEqual(d.model, 'isr4200') + self.assertEqual(d.pid, 'ISR4221/K9') + def test_isr_controller_mode_connect(self): testbed = ''' @@ -71,8 +104,11 @@ def test_isr_login_connect(self): start=['mock_device_cli --os iosxe --state isr_login --hostname Router'], os='iosxe', credentials=dict(default=dict(username='cisco', password='cisco'))) - c.connect() - self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') + try: + c.connect() + self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') + finally: + c.disconnect() def test_edison_login_connect(self): c = Connection(hostname='Router', @@ -80,8 +116,11 @@ def test_edison_login_connect(self): os='iosxe', platform='cat3k', credentials=dict(default=dict(username='cisco', password='cisco'))) - c.connect() - self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') + try: + c.connect() + self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') + finally: + c.disconnect() def test_edison_login_connect_password_ok(self): c = Connection(hostname='Router', @@ -89,31 +128,44 @@ def test_edison_login_connect_password_ok(self): os='iosxe', platform='cat3k', credentials=dict(default=dict(username='cisco', password='cisco'))) - c.connect() - self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') + try: + c.connect() + self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') + finally: + c.disconnect() def test_general_login_connect(self): c = Connection(hostname='Router', start=['mock_device_cli --os iosxe --state general_login --hostname Router'], os='iosxe', credentials=dict(default=dict(username='cisco', password='cisco'))) - c.connect() - self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') + try: + c.connect() + self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') + finally: + c.disconnect() def test_general_login_connect_syslog(self): c = Connection(hostname='Router', start=['mock_device_cli --os iosxe --state connect_syslog --hostname Router'], os='iosxe', credentials=dict(default=dict(username='cisco', password='cisco'))) - c.connect() - self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') + try: + c.connect() + self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') + finally: + c.disconnect() def test_general_configure(self): c = Connection(hostname='Router', start=['mock_device_cli --os iosxe --state general_login --hostname Router'], os='iosxe', credentials=dict(default=dict(username='cisco', password='cisco'))) - c.connect() + try: + c.connect() + finally: + c.disconnect() + cmd = ['crypto key generate rsa general-keys modulus 2048 label ca', 'crypto pki server ca', 'grant auto', 'hash sha256', 'lifetime ca-certificate 3650', 'lifetime certificate 3650', 'database archive pkcs12 password 0 cisco123', 'no shutdown'] @@ -125,25 +177,32 @@ def test_general_config_ca_profile(self): start=['mock_device_cli --os iosxe --state general_login --hostname Router'], os='iosxe', credentials=dict(default=dict(username='cisco', password='cisco'))) - c.connect() - c.configure("crypto pki profile enrollment test", timeout=60) - self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') + + try: + c.connect() + c.configure("crypto pki profile enrollment test", timeout=60) + self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') + finally: + c.disconnect() def test_gkm_local_server(self): c = Connection(hostname='Router', start=['mock_device_cli --os iosxe --state general_login --hostname Router'], os='iosxe', credentials=dict(default=dict(username='cisco', password='cisco'))) - c.connect() - cmd = [ - "crypto gkm group g1", - "identity number 101", - "server local", - "end", - "end" - ] - c.configure(cmd, timeout=60) - self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') + try: + c.connect() + cmd = [ + "crypto gkm group g1", + "identity number 101", + "server local", + "end", + "end" + ] + c.configure(cmd, timeout=60) + self.assertEqual(c.spawn.match.match_output, 'end\r\nRouter#') + finally: + c.disconnect() def test_login_console_server_sendline_after(self): md = MockDeviceTcpWrapperIOSXE(port=0, state='ts_login') @@ -185,6 +244,16 @@ def test_login_console_server_post_cred_action(self): c.disconnect() md.stop() + def test_operating_mode_check(self): + c = Connection(hostname='Router', + start=['mock_device_cli --os iosxe --state general_enable_no_operating_mode --hostname Router'], + os='iosxe', + credentials=dict(default=dict(username='cisco', password='cisco'))) + try: + c.connect() + finally: + c.disconnect() + class TestIosXEPluginExecute(unittest.TestCase): @@ -755,6 +824,21 @@ def test_slow_config_mode(self): c.configure(['no logging console']) c.disconnect() + def test_slow_config_mode2(self): + c = Connection(hostname='Switch', + start=['mock_device_cli --os iosxe --state slow_config_mode2 --hostname Switch'], + os='iosxe', + mit=True, + init_exec_commands=[], + init_config_commands=[], + log_buffer=True, + settings=dict(CONFIG_TIMEOUT=10), + debug=True + ) + c.connect() + c.configure(['no logging console']) + c.disconnect() + def test_slow_config_lock(self): md = MockDeviceTcpWrapperIOSXE(port=0, state='config_locked') md.start() @@ -879,7 +963,20 @@ def test_configure_wsma_agent_exec(self): c.configure('wsma agent exec') c.disconnect() - + def test_configure_pki_hexmode(self): + c = Connection(hostname='Switch', + start=['mock_device_cli --os iosxe --state general_enable'], + os='iosxe', + mit=True, + init_exec_commands=[], + init_config_commands=[], + log_buffer=True, + ) + try: + c.connect() + c.configure(['crypto pki certificate chain SLA-TrustPoint', 'certificate ca 01']) + finally: + c.disconnect() class TestIosXEEnableSecret(unittest.TestCase): def test_enable_secret(self): diff --git a/src/unicon/plugins/tests/test_plugin_iosxe_c9800_ewc.py b/src/unicon/plugins/tests/test_plugin_iosxe_cat9k_c9100ap.py similarity index 85% rename from src/unicon/plugins/tests/test_plugin_iosxe_c9800_ewc.py rename to src/unicon/plugins/tests/test_plugin_iosxe_cat9k_c9100ap.py index c4063c46..ff78e61b 100644 --- a/src/unicon/plugins/tests/test_plugin_iosxe_c9800_ewc.py +++ b/src/unicon/plugins/tests/test_plugin_iosxe_cat9k_c9100ap.py @@ -8,14 +8,14 @@ unicon.settings.Settings.GRACEFUL_DISCONNECT_WAIT_SEC = 0.2 -class TestPluginIosXeEwc(unittest.TestCase): +class TestPluginIosXeCat9kC9100AP(unittest.TestCase): def test_connect(self): conn = Connection( hostname='EWC', os='iosxe', - platform='c9800', - model='ewc_ap', + platform='cat9k', + model='c9100ap', start=['mock_device_cli --os iosxe --state ewc_enable'], learn_hostname=True) conn.connect() @@ -25,8 +25,8 @@ def test_ap_shell(self): conn = Connection( hostname='EWC', os='iosxe', - platform='c9800', - model='ewc_ap', + platform='cat9k', + model='c9100ap', start=['mock_device_cli --os iosxe --state ewc_enable'], credentials=dict(ap=dict(username='lab', password='lab', enable_password='lab')), learn_hostname=True) @@ -41,8 +41,8 @@ def test_bash_console(self): conn = Connection( hostname='EWC', os='iosxe', - platform='c9800', - model='ewc_ap', + platform='cat9k', + model='c9100ap', start=['mock_device_cli --os iosxe --state ewc_enable'], credentials=dict(ap=dict(username='lab', password='lab', enable_password='lab')), learn_hostname=True) diff --git a/src/unicon/plugins/tests/test_plugin_iosxr.py b/src/unicon/plugins/tests/test_plugin_iosxr.py index 422f4200..2b404d22 100644 --- a/src/unicon/plugins/tests/test_plugin_iosxr.py +++ b/src/unicon/plugins/tests/test_plugin_iosxr.py @@ -199,6 +199,16 @@ def test_failed_config(self): self._conn.spawn.timeout = 60 self._conn.enable() + def test_failed_config_error_message1(self): + """Check that we can successfully return to an enable prompt after entering failed config.""" + with self.assertRaisesRegex(unicon.core.errors.SubCommandFailure, "% Invalid config"): + self._conn.configure("test failed") + + def test_failed_config_error_message2(self): + """Check that we can successfully return to an enable prompt after entering failed config.""" + with self.assertRaisesRegex(unicon.core.errors.SubCommandFailure, "% Invalid config"): + self._conn.configure("test failed2") + class TestIosXrPluginAdminService(unittest.TestCase): def test_admin(self): @@ -527,6 +537,12 @@ def test_config_commit_replace(self): self.assertEqual(self.conn.configure.commit_cmd, 'commit replace') self.assertEqual(self.ha_dev.configure.commit_cmd, 'commit replace') + def test_config_commit_best_effort(self): + self.conn.configure('no logging console', best_effort=True) + self.ha_dev.configure('no logging console', best_effort=True) + self.assertEqual(self.conn.configure.commit_cmd, 'commit best-effort') + self.assertEqual(self.ha_dev.configure.commit_cmd, 'commit best-effort') + def test_config_commit_force(self): self.conn.configure('no logging console', force=True) self.ha_dev.configure('no logging console', force=True) diff --git a/src/unicon/plugins/tests/test_utils.py b/src/unicon/plugins/tests/test_utils.py index 1f543941..0eb94b13 100644 --- a/src/unicon/plugins/tests/test_utils.py +++ b/src/unicon/plugins/tests/test_utils.py @@ -73,6 +73,7 @@ def test_ios_learn_tokens_from_show_version(self): # Test connection succeeds and tokens learned self.dev.connect(learn_tokens=True, learn_hostname=True) + self.assertEqual(self.dev.state_machine.current_state, 'enable') self.assertEqual(self.dev.os, 'ios') self.assertEqual(self.dev.version, '15') self.assertEqual(self.dev.platform, 'c7200p') diff --git a/src/unicon/plugins/utils.py b/src/unicon/plugins/utils.py index 4933dd0f..9d3d9315 100644 --- a/src/unicon/plugins/utils.py +++ b/src/unicon/plugins/utils.py @@ -217,13 +217,14 @@ def load_token_csv_file(file_path, key='pid'): return ret_dict def get_device_mode(con): - '''Check the mode of device + '''Check the mode of device ''' output = con.execute('show version | include operating mode') if output: pattern = re.compile(r'.*operating mode:\s*(?P[\w-]+).*', re.DOTALL) m = pattern.match(output) - return m.groupdict().get('mode') + if m: + return m.groupdict().get('mode') class AbstractTokenDiscovery(): @@ -307,7 +308,23 @@ def discover_tokens(self): discovery_prompt_stmt = \ Statement(pattern=self.con.state_machine\ .get_state('learn_tokens_state').pattern) - dialog = Dialog([discovery_prompt_stmt, generic_statements.more_prompt_stmt]) + dialog = Dialog([ + discovery_prompt_stmt, + generic_statements.more_prompt_stmt, + generic_statements.syslog_msg_stmt + ]) + + # Try to get to enable mode, ignore failure + from unicon.plugins.generic.statements import generic_statements + enable_dialog = dialog + Dialog([ + generic_statements.enable_password_stmt, + generic_statements.syslog_msg_stmt + ]) + try: + self.con.sendline('enable') + enable_dialog.process(self.con.spawn, context=self.con.context) + except Exception: + pass # Execute the command on the device for cmd in self.commands_and_classes: @@ -319,8 +336,8 @@ def discover_tokens(self): continue else: outcome = dialog.process(self.con.spawn, - timeout=self.con.spawn.settings.EXEC_TIMEOUT, - prompt_recovery=True) + timeout=self.con.spawn.settings.EXEC_TIMEOUT + ) if not outcome.match_output: continue @@ -538,5 +555,3 @@ def learn_device_tokens(self, overwrite_testbed_tokens=False): # Show the results of the process self.show_results() return self.learned_tokens - -