From a697068d672f1dc69666620c77bae1f9d289cfab Mon Sep 17 00:00:00 2001 From: omid Date: Mon, 3 Feb 2025 16:40:27 -0500 Subject: [PATCH] Releasing v25.1 --- Makefile | 2 +- docs/changelog/2025/january.rst | 61 +++++ docs/changelog/index.rst | 1 + docs/changelog_plugins/2025/january.rst | 42 +++ ...e_rommon_prompt_pattern_20241028185708.rst | 5 + docs/changelog_plugins/index.rst | 1 + docs/user_guide/services/iosxr.rst | 2 + setup.py | 3 +- src/unicon/plugins/__init__.py | 2 +- src/unicon/plugins/confd/settings.py | 2 +- .../fxos/ftd/service_implementation.py | 6 +- src/unicon/plugins/generic/patterns.py | 8 +- .../plugins/generic/service_implementation.py | 8 +- .../plugins/generic/service_patterns.py | 4 +- .../plugins/generic/service_statements.py | 9 +- src/unicon/plugins/generic/settings.py | 2 +- .../iosxe/cat8k/service_implementation.py | 4 +- src/unicon/plugins/iosxe/patterns.py | 2 +- src/unicon/plugins/iosxe/settings.py | 4 +- src/unicon/plugins/iosxr/patterns.py | 6 +- .../plugins/iosxr/service_implementation.py | 31 ++- src/unicon/plugins/iosxr/service_patterns.py | 2 + .../plugins/iosxr/service_statements.py | 19 +- src/unicon/plugins/iosxr/settings.py | 1 + src/unicon/plugins/iosxr/spitfire/patterns.py | 2 + .../plugins/iosxr/spitfire/statements.py | 11 + src/unicon/plugins/iosxr/statemachine.py | 5 +- src/unicon/plugins/iosxr/statements.py | 5 + .../plugins/nxos/service_implementation.py | 7 +- src/unicon/plugins/ons/connection_provider.py | 2 +- .../generic/generic_mock_data_ios.yaml | 4 + .../mock_data/iosxe/iosxe_mock_cat9k.yaml | 45 +++- .../mock_data/iosxe/iosxe_mock_copy.yaml | 16 ++ .../mock_data/iosxe/iosxe_mock_data.yaml | 26 +- .../mock_data/iosxr/iosxr_mock_data.yaml | 36 +++ .../iosxr/iosxr_mock_data_standby.yaml | 245 ++++++++++++++++++ src/unicon/plugins/tests/test_copy_service.py | 33 ++- src/unicon/plugins/tests/test_plugin_confd.py | 4 +- src/unicon/plugins/tests/test_plugin_iosxe.py | 12 + .../plugins/tests/test_plugin_iosxe_cat9k.py | 21 ++ src/unicon/plugins/tests/test_plugin_iosxr.py | 46 +++- .../plugins/tests/test_plugin_iosxr_ha.py | 42 +++ src/unicon/plugins/tests/test_utils.py | 13 + 43 files changed, 755 insertions(+), 47 deletions(-) create mode 100644 docs/changelog/2025/january.rst create mode 100644 docs/changelog_plugins/2025/january.rst create mode 100644 docs/changelog_plugins/changelog_modify_generice_rommon_prompt_pattern_20241028185708.rst diff --git a/Makefile b/Makefile index 39efbd0d..0dfc069b 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ PYPIREPO = pypitest DEPENDENCIES = robotframework pyyaml dill coverage Sphinx \ sphinxcontrib-napoleon sphinxcontrib-mockautodoc \ - sphinx-rtd-theme asyncssh PrettyTable + sphinx-rtd-theme asyncssh PrettyTable "cryptography>=44.0" .PHONY: clean package distribute develop undevelop help devnet\ diff --git a/docs/changelog/2025/january.rst b/docs/changelog/2025/january.rst new file mode 100644 index 00000000..8c1ef435 --- /dev/null +++ b/docs/changelog/2025/january.rst @@ -0,0 +1,61 @@ +January 2025 +========== + + - Unicon v25.1 +------------------------ + + + +.. csv-table:: Module Versions + :header: "Modules", "Versions" + + ``unicon.plugins``, v25.1 + ``unicon``, v25.1 + + + + +Changelogs +^^^^^^^^^^ +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* backend + * New match mode support for last line ignoring whitespace + +* learn_tokens + * Update learn_os_prompt to account for config mode + +* unicon + * Fix the dialog processor to trigger actions only when statements match patterns(HA/Stack) + + +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* iosxr + * Added SwitchoverDisallowedError exception to raise when redundancy switchover is disallowed on the device. + +* unicon.plugins + * Added Reload + * Added support to pick max value of RELOAD_RECONNECT_WAIT or POST_RELOAD_WAIT + * Base Execute + * pass backend decode error + * generic + * Updated regex patterns to prevent matching of test case names that contain the words "failure" or "fail_". This change ensures that test cases with failure-related names no longer trigger errors during processing. + +* iosxe/pattern + * Allow 'DDNS' to config prompt patterns + +* generic + * Added 'copy_overwrite_handler' in the service_statements.py to handle + +* iosxe + * Added below config error patterns + * % VLAN [] already in use + * Added below config error patterns + * % VNI is either already in use or exceeds the maximum allowable VNIs. + + diff --git a/docs/changelog/index.rst b/docs/changelog/index.rst index 64691bd6..a0fa018b 100644 --- a/docs/changelog/index.rst +++ b/docs/changelog/index.rst @@ -4,6 +4,7 @@ Changelog .. toctree:: :maxdepth: 2 + 2025/january 2024/november 2024/october 2024/September diff --git a/docs/changelog_plugins/2025/january.rst b/docs/changelog_plugins/2025/january.rst new file mode 100644 index 00000000..e180b782 --- /dev/null +++ b/docs/changelog_plugins/2025/january.rst @@ -0,0 +1,42 @@ +January 2025 +========== + + - Unicon.Plugins v25.1 +------------------------ + + + +.. csv-table:: Module Versions + :header: "Modules", "Versions" + + ``unicon.plugins``, v25.1 + ``unicon``, v25.1 + + + + +Changelogs +^^^^^^^^^^ +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* iosxr + * Update monitor service prompt pattern + * Fix action pattern regex + * Update logic support matching case and space insensitive actions + * SPITFIRE plugin + * Added a new pattern to recognize the prompt seen when showtech collection times out and the script tries to exit by sending kill signal. Also, added the statement to run while the pattern matches + * Added UNICON_BACKEND_DECODE_ERROR_LIMIT with a default value of 10, to handle scenarios when the device is slow + * Add statements to reload dialog + * Add pattern for "Do you wish to continue" + * Add syslog statement to config state transition + +* generic + * Update learn_os_prompt to account for config mode + * update syslog message pattern + +* unicon.plugins + * Fix syntax warning + + diff --git a/docs/changelog_plugins/changelog_modify_generice_rommon_prompt_pattern_20241028185708.rst b/docs/changelog_plugins/changelog_modify_generice_rommon_prompt_pattern_20241028185708.rst new file mode 100644 index 00000000..4ba1e475 --- /dev/null +++ b/docs/changelog_plugins/changelog_modify_generice_rommon_prompt_pattern_20241028185708.rst @@ -0,0 +1,5 @@ +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- +* Generic + * Modified rommon_prompt regex pattern to accommodate various outputs diff --git a/docs/changelog_plugins/index.rst b/docs/changelog_plugins/index.rst index b0b7b047..e663d67b 100644 --- a/docs/changelog_plugins/index.rst +++ b/docs/changelog_plugins/index.rst @@ -4,6 +4,7 @@ Plugins Changelog .. toctree:: :maxdepth: 2 + 2025/january 2024/november 2024/october 2024/September diff --git a/docs/user_guide/services/iosxr.rst b/docs/user_guide/services/iosxr.rst index 606004a7..3dee3f3d 100644 --- a/docs/user_guide/services/iosxr.rst +++ b/docs/user_guide/services/iosxr.rst @@ -172,6 +172,8 @@ Example: # send an action to the device rtr.monitor('clear') rtr.monitor('bytes') + rtr.monitor('general') + rtr.monitor('IPv4 uni') # this can be called with 'ipv4 uni' or 'ipv4uni' as well. monitor.get_buffer diff --git a/setup.py b/setup.py index 41881e21..26355090 100755 --- a/setup.py +++ b/setup.py @@ -50,7 +50,8 @@ def version_info(*paths): install_requires = ['unicon {range}'.format(range = version_range), 'pyyaml', - 'PrettyTable'] + 'PrettyTable', + 'cryptography>=44.0'] # launch setup setup( diff --git a/src/unicon/plugins/__init__.py b/src/unicon/plugins/__init__.py index 53f9d7d7..2c4f89c7 100644 --- a/src/unicon/plugins/__init__.py +++ b/src/unicon/plugins/__init__.py @@ -1,4 +1,4 @@ -__version__ = '24.11' +__version__ = '25.1' supported_chassis = [ 'single_rp', diff --git a/src/unicon/plugins/confd/settings.py b/src/unicon/plugins/confd/settings.py index 0bc1ac97..235b5353 100644 --- a/src/unicon/plugins/confd/settings.py +++ b/src/unicon/plugins/confd/settings.py @@ -27,7 +27,7 @@ def __init__(self): self.JUNIPER_INIT_CONFIG_COMMANDS = [] # Prompt prefixes will be removed from the output by the configure() and execute() services - self.JUNIPER_PROMPT_PREFIX = "\[edit\]" + self.JUNIPER_PROMPT_PREFIX = r"\[edit\]" self.ERROR_PATTERN = [ 'Error:', diff --git a/src/unicon/plugins/fxos/ftd/service_implementation.py b/src/unicon/plugins/fxos/ftd/service_implementation.py index 4adf2137..fc010275 100644 --- a/src/unicon/plugins/fxos/ftd/service_implementation.py +++ b/src/unicon/plugins/fxos/ftd/service_implementation.py @@ -61,9 +61,9 @@ def call_service(self, target, raise Exception('Invalid switchto target type: %s' % repr(target)) for target_state in target_list: - m1 = re.match('module\s+(\d+)\s+console', target_state) - m2 = re.match('cimc\s+(\S+)', target_state) - m3 = re.match('chassis scope (.*)', target_state) + m1 = re.match(r'module\s+(\d+)\s+console', target_state) + m2 = re.match(r'cimc\s+(\S+)', target_state) + m3 = re.match(r'chassis scope (.*)', target_state) if m1: mod = m1.group(1) self.context._module = mod diff --git a/src/unicon/plugins/generic/patterns.py b/src/unicon/plugins/generic/patterns.py index 471ef4b0..bc9099ce 100644 --- a/src/unicon/plugins/generic/patterns.py +++ b/src/unicon/plugins/generic/patterns.py @@ -33,7 +33,7 @@ def __init__(self): # self.config_prompt = r'.*%N\(config.*\)#\s?$' self.config_prompt = r'^(.*)\(.*(con|cfg|ipsec-profile|ca-trustpoint|gkm-local-server)\S*\)#\s?$' - self.rommon_prompt = r'^(.*?)(rommon[\s\d]*>|switch:|grub>)\s?$' + self.rommon_prompt = r'^(.*?)(rommon[\s\d]*>|switch:|grub>)\s*(\x1b\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|This \(D\)RP Node is not ready or active for login \/configuration.*)' @@ -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+)?)$' + self.learn_os_prompt = r'^(.*?(?\$~%]|[^#\s]#|~ #|~/|^admin:|^#)\s?(\x1b\S+)?)$' self.sudo_password_prompt = r'^.*(\[sudo\] password for .*?:|This is your UNIX password:)\s*$' @@ -67,7 +67,9 @@ def __init__(self): # %Error opening tftp://255.255.255.255/network-confg (Timed out) # %Error opening tftp://255.255.255.255/cisconet.cfg (Timed out) # %Error opening tftp://255.255.255.255/switch-confg (Timed out) - self.syslog_message_pattern = r'^.*?(%\w+(-\S+)?-\d+-\w+|Guestshell destroyed successfully|%Error opening tftp:\/\/255\.255\.255\.255|Autoinstall trying|audit: kauditd hold queue overflow).*$' + # LC/0/2/CPU0:Sep 10 00:54:42.841 + # RP/0/0/CPU0:Oct 9 01:44:47.875 + self.syslog_message_pattern = r'^.*?(%\w+(-\S+)?-\d+-\w+|Guestshell destroyed successfully|%Error opening tftp:\/\/255\.255\.255\.255|Autoinstall trying|audit: kauditd hold queue overflow|(LC|RP)/\d+/\d+/CPU\d+:\w+\s+\d+\s+\d{2}:\d{2}:\d{2}).*\s*$' self.config_locked = r'Configuration (mode )?(is )?locked|Config mode cannot be entered' diff --git a/src/unicon/plugins/generic/service_implementation.py b/src/unicon/plugins/generic/service_implementation.py index f2d86a55..a216f867 100644 --- a/src/unicon/plugins/generic/service_implementation.py +++ b/src/unicon/plugins/generic/service_implementation.py @@ -736,8 +736,10 @@ def call_service(self, command=[], # noqa: C901 self.result = dialog_match.match_output self.result = self.get_service_result() sm.detect_state(con.spawn, con.context) - except (StateMachineError, UniconBackendDecodeError): + except StateMachineError: raise + except UniconBackendDecodeError: + pass except Exception as err: raise SubCommandFailure("Command execution failed", err) from err @@ -1579,7 +1581,9 @@ def call_service(self, reply=Dialog([]), *args, **kwargs): # noqa: C901 elif a == "erase": copy_context[a] = "n" elif a == 'overwrite': - copy_context[a] = True + # To Handle overwrite = False condition + overwrite = kwargs.get('overwrite', True) + copy_context[a] = overwrite elif a == 'vrf': copy_context[a] = "Mgmt-intf" elif a == 'timeout': diff --git a/src/unicon/plugins/generic/service_patterns.py b/src/unicon/plugins/generic/service_patterns.py index 7d73ad8b..c8a8845d 100644 --- a/src/unicon/plugins/generic/service_patterns.py +++ b/src/unicon/plugins/generic/service_patterns.py @@ -156,11 +156,11 @@ def __init__(self): self.copy_proceed = r'^.*bytes.*proceed\?.*$' self.tftp_addr =r'^.*Address or name of remote host \[\]\?\s*$' self.copy_complete = r'^.*bank [0-9]+' - self.copy_error_message = r'fail|timed out|Timed out|Error|Login incorrect|denied|Problem' \ + self.copy_error_message = r'\bfail\b|timed out|Timed out|Error|Login incorrect|denied|Problem' \ r'|NOT|Invalid|No memory|Failed(?! to generate persistent self-signed certificate)|mismatch|Bad|bogus|lose|abort' \ r'|Not |too big|exceeds|detected|[Nn]o route to host' \ r'|image is not allowed|Could not resolve|No such' - self.copy_retry_message = r'fail|[Tt]imed out|Error|Problem|NOT|Failed(?! to generate persistent self-signed certificate)|Bad|bogus|lose|abort|Not |too big|exceeds|detected' + self.copy_retry_message = r'\bfail\b|[Tt]imed out|Error|Problem|NOT|Failed(?! to generate persistent self-signed certificate)|Bad|bogus|lose|abort|Not |too big|exceeds|detected' self.copy_continue = r'Are you sure you want to continue connecting ((yes/no)|\((yes/no(/\[fingerprint\])?)?\))?' self.copy_other = r'^.*\[yes\/no\]\s*\?*\s*$' self.remote_param ='ftp:|tftp:|http:|rcp:|scp:' diff --git a/src/unicon/plugins/generic/service_statements.py b/src/unicon/plugins/generic/service_statements.py index a18fd4d4..631be71b 100644 --- a/src/unicon/plugins/generic/service_statements.py +++ b/src/unicon/plugins/generic/service_statements.py @@ -166,6 +166,11 @@ def copy_handler_1(spawn, context, send_key): else: raise SubCommandFailure("%s is not specified" % context[send_key]) +def copy_overwrite_handler(spawn, context): + if context['overwrite'] == 'False': + spawn.sendline('n') + else: + spawn.sendline('y') def copy_error_handler(context, retry=False): if retry: @@ -961,8 +966,8 @@ def config_session_locked_handler(context): continue_timer=True) # Recheck this copy_overwrite = Statement(pattern=pat.copy_overwrite, - action=send_response, - args={'response': 'y'}, + action=copy_overwrite_handler, + args=None, loop_continue=True, continue_timer=True) diff --git a/src/unicon/plugins/generic/settings.py b/src/unicon/plugins/generic/settings.py index f5bf7781..2bb32dce 100644 --- a/src/unicon/plugins/generic/settings.py +++ b/src/unicon/plugins/generic/settings.py @@ -89,7 +89,7 @@ def __init__(self): 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 + self.ESCAPE_CHAR_CHATTY_TERM_WAIT_RETRIES = 6 # prompt wait delay self.ESCAPE_CHAR_PROMPT_WAIT = 1 diff --git a/src/unicon/plugins/iosxe/cat8k/service_implementation.py b/src/unicon/plugins/iosxe/cat8k/service_implementation.py index 55f14090..e32aace2 100644 --- a/src/unicon/plugins/iosxe/cat8k/service_implementation.py +++ b/src/unicon/plugins/iosxe/cat8k/service_implementation.py @@ -87,7 +87,7 @@ def call_service(self, command=None, # Check if switchover is possible by checking if "IOSXE_DUAL_IOS = 1" is # in the output of 'sh romvar' output = con.execute('show romvar') - if not re.search('IOSXE_DUAL_IOS\s*=\s*1', output): + if not re.search(r'IOSXE_DUAL_IOS\s*=\s*1', output): raise SubCommandFailure( "Switchover can't be issued if IOSXE_DUAL_IOS is not activated") @@ -147,7 +147,7 @@ def call_service(self, command=None, sleep(sleep_per_interval) continue else: - if not re.search('R\d+/\d+\s+init,\s*standby.*', output): + if not re.search(r'R\d+/\d+\s+init,\s*standby.*', output): break elif interval * sleep_per_interval < standby_wait_time: con.log.info( diff --git a/src/unicon/plugins/iosxe/patterns.py b/src/unicon/plugins/iosxe/patterns.py index c594a812..5258d44a 100644 --- a/src/unicon/plugins/iosxe/patterns.py +++ b/src/unicon/plugins/iosxe/patterns.py @@ -29,7 +29,7 @@ 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'^(.*)\((?!.*pki-hexmode).*(con|cfg|ipsec-profile|ca-trustpoint|ca-certificate-map|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|ca-certificate-map|cs-server|ca-profile|gkm-local-server|cloud|host-list|config-gkm-group|gkm-sa-ipsec|gdoi-coop-ks-config|wsma|enforce-rule|DDNS)\S*\)#\s?$' self.config_pki_prompt = r'^(.*)\(config-pki-hexmode\)#\s?$' diff --git a/src/unicon/plugins/iosxe/settings.py b/src/unicon/plugins/iosxe/settings.py index 82552f97..9293b2ff 100644 --- a/src/unicon/plugins/iosxe/settings.py +++ b/src/unicon/plugins/iosxe/settings.py @@ -34,7 +34,9 @@ def __init__(self): r'% Policy commands not allowed without an address family', r'% Color set already. Deconfigure first', r'Invalid policy name, \S+ does not exist', - r'% Deletion of RD in progress; wait for it to complete' + r'% Deletion of RD in progress; wait for it to complete', + r'% VLAN \[\d+\] already in use', + r'% VNI \d+ is either already in use or exceeds the maximum allowable VNIs.' ] self.EXECUTE_MATCHED_RETRIES = 1 diff --git a/src/unicon/plugins/iosxr/patterns.py b/src/unicon/plugins/iosxr/patterns.py index 19f6f9d9..e191df48 100755 --- a/src/unicon/plugins/iosxr/patterns.py +++ b/src/unicon/plugins/iosxr/patterns.py @@ -49,8 +49,10 @@ def __init__(self): # Brief='b', Detail='d', Protocol(IPv4/IPv6)='r' # Brief='b', Detail='d', Protocol(IPv4/IPv6)='r'\x1b[K\r\n\x1b[K\r\n # (General='g', IPv4 Uni='4u', IPv4 Multi='4m', IPv6 Uni='6u', IPv6 Multi='6m') - self.monitor_prompt = r"^(.*?)(Brief='b', Detail='d', Protocol\(IPv4/IPv6\)='r'|\(General='g', IPv4 Uni='4u', IPv4 Multi='4m', IPv6 Uni='6u', IPv6 Multi='6m'\))(\x1b\S+[\r\n]+)*$" + # This pattern does not end with $ on purpose as the prompt is part of the 'live' output + # and the output is updated frequently + self.monitor_prompt = r"^(.*?)(Brief='b', Detail='d', Protocol\(IPv4/IPv6\)='r'|\(General='g', IPv4 Uni='4u', IPv4 Multi='4m', IPv6 Uni='6u', IPv6 Multi='6m'\))(\x1b\S+[\r\n]+)*" # r1 Monitor Time: 00:00:06 SysUptime: 15:48:49 self.monitor_time_regex = r'(?P\S+).*?Monitor Time: (?P