Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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\
Expand Down
61 changes: 61 additions & 0 deletions docs/changelog/2025/january.rst
Original file line number Diff line number Diff line change
@@ -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 [<vlan_id>] already in use
* Added below config error patterns
* % VNI <VNI_ID> is either already in use or exceeds the maximum allowable VNIs.


1 change: 1 addition & 0 deletions docs/changelog/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Changelog
.. toctree::
:maxdepth: 2

2025/january
2024/november
2024/october
2024/September
Expand Down
42 changes: 42 additions & 0 deletions docs/changelog_plugins/2025/january.rst
Original file line number Diff line number Diff line change
@@ -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


Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
--------------------------------------------------------------------------------
Fix
--------------------------------------------------------------------------------
* Generic
* Modified rommon_prompt regex pattern to accommodate various outputs
1 change: 1 addition & 0 deletions docs/changelog_plugins/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Plugins Changelog
.. toctree::
:maxdepth: 2

2025/january
2024/november
2024/october
2024/September
Expand Down
2 changes: 2 additions & 0 deletions docs/user_guide/services/iosxr.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion src/unicon/plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = '24.11'
__version__ = '25.1'

supported_chassis = [
'single_rp',
Expand Down
2 changes: 1 addition & 1 deletion src/unicon/plugins/confd/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:',
Expand Down
6 changes: 3 additions & 3 deletions src/unicon/plugins/fxos/ftd/service_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions src/unicon/plugins/generic/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.*)'
Expand All @@ -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'^(.*?(?<!config)(?<!conf)([>\$~%]|[^#\s]#|~ #|~/|^admin:|^#)\s?(\x1b\S+)?)$'

self.sudo_password_prompt = r'^.*(\[sudo\] password for .*?:|This is your UNIX password:)\s*$'

Expand All @@ -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'

Expand Down
8 changes: 6 additions & 2 deletions src/unicon/plugins/generic/service_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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':
Expand Down
4 changes: 2 additions & 2 deletions src/unicon/plugins/generic/service_patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:'
Expand Down
9 changes: 7 additions & 2 deletions src/unicon/plugins/generic/service_statements.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion src/unicon/plugins/generic/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/unicon/plugins/iosxe/cat8k/service_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion src/unicon/plugins/iosxe/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -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?$'
Expand Down
4 changes: 3 additions & 1 deletion src/unicon/plugins/iosxe/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions src/unicon/plugins/iosxr/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -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<hostname>\S+).*?Monitor Time: (?P<time>\d+:\d+:\d+).*?SysUptime: (?P<uptime>\S+)'
# Quit='q', Freeze='f', Thaw='t', Clear='c', Interface='i',
self.monitor_command_pattern = r"(?P<command>\S+)='(?P<key>\w)'"
self.monitor_command_pattern = r"\s*(?P<command>[\w\s]+)='(?P<key>\w+)'"
Loading
Loading