Skip to content

Releasing v25.2 #110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 11, 2025
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 "cryptography>=44.0"
sphinx-rtd-theme asyncssh PrettyTable "cryptography>=43.0"


.PHONY: clean package distribute develop undevelop help devnet\
Expand Down
45 changes: 45 additions & 0 deletions docs/changelog/2025/february.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
February 2025
==========

February 25 - Unicon v25.2
------------------------



.. csv-table:: Module Versions
:header: "Modules", "Versions"

``unicon.plugins``, v25.2
``unicon``, v25.2




Changelogs
^^^^^^^^^^
--------------------------------------------------------------------------------
Fix
--------------------------------------------------------------------------------

* router.connection_provider
* Modified disconnect
* Added sendline('exit') on disconnect


--------------------------------------------------------------------------------
Fix
--------------------------------------------------------------------------------

* generic
* updated exception for Recover device using golden image if reload is failed.


--------------------------------------------------------------------------------
New
--------------------------------------------------------------------------------

* iosxe
* Added to Configure Error Patterns
* Added the regex to match error pattern "127.0 / 255.0 is an invalid network."


4 changes: 1 addition & 3 deletions docs/changelog/2025/january.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,4 @@ Changelogs
* 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.


* % 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/february
2025/january
2024/november
2024/october
Expand Down
36 changes: 36 additions & 0 deletions docs/changelog_plugins/2025/february.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
February 2025
==========

February 25 - Unicon.Plugins v25.2
------------------------



.. csv-table:: Module Versions
:header: "Modules", "Versions"

``unicon.plugins``, v25.2
``unicon``, v25.2




Changelogs
^^^^^^^^^^
--------------------------------------------------------------------------------
Fix
--------------------------------------------------------------------------------

* generic
* Updates to setup patterns
* Update connection refused handler to clear line after max count
* Update token discovery to use rv1 parsers
* Update token discovery to handle standby locked devices

* staros
* Update prompt pattern

* iosxe
* Updated the enable, disable and maintenance states to support `(unlicensed)` prompt


4 changes: 1 addition & 3 deletions docs/changelog_plugins/2025/january.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,4 @@ Changelogs
* update syslog message pattern

* unicon.plugins
* Fix syntax warning


* Fix syntax warning
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/february
2025/january
2024/november
2024/october
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def version_info(*paths):
install_requires = ['unicon {range}'.format(range = version_range),
'pyyaml',
'PrettyTable',
'cryptography>=44.0']
'cryptography>=43.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__ = '25.1'
__version__ = '25.2'

supported_chassis = [
'single_rp',
Expand Down
4 changes: 2 additions & 2 deletions src/unicon/plugins/generic/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ def __init__(self):
self.press_ctrlx = r"^(.*?)Press Ctrl\+x to Exit the session"
self.connected = r'^(.*?)Connected.'

self.enter_basic_mgmt_setup = r'Would you like to enter basic management setup\? \[yes/no\]:'
self.kerberos_no_realm = r'^(.*)Kerberos:\s*No default realm defined for Kerberos!'
self.enter_basic_mgmt_setup = r'Would you like to enter basic management setup\? \[yes/no\]:\s*$'
self.kerberos_no_realm = r'^(.*)Kerberos:\s*No default realm defined for Kerberos!\s*$'

self.passphrase_prompt = r'^.*Enter passphrase for key .*?:\s*?'

Expand Down
3 changes: 2 additions & 1 deletion src/unicon/plugins/generic/service_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1172,7 +1172,8 @@ def call_service(self,
except Exception as e:
if hasattr(con.device, 'clean') and hasattr(con.device.clean, 'device_recovery') and\
con.device.clean.device_recovery.get('golden_image'):
con.log.error(f'Reload failed booting device using golden image: {con.device.clean.device_recovery["golden_image"]}')
con.log.exception(f"Reload failed to install with file: {getattr(con.device.clean, 'images', [None])[0]}")
con.log.info(f'Booting the device using golden_image.')
con.device.api.device_recovery_boot(golden_image=con.device.clean.device_recovery['golden_image'])
con.log.info('Successfully booted the device using golden_image.')
raise
Expand Down
1 change: 1 addition & 0 deletions src/unicon/plugins/generic/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def __init__(self):
self.CONSOLE_TIMEOUT = 60
self.BOOT_TIMEOUT = 600
self.MAX_BOOT_ATTEMPTS = 3
self.CONNECTION_REFUSED_MAX_COUNT = 3

# Temporary enable secret used during setup
# this is used if no password is available
Expand Down
13 changes: 8 additions & 5 deletions src/unicon/plugins/generic/statements.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,16 @@ def terminal_position_handler(spawn, session, context):
spawn.send('\x1b[0;200R')


def connection_refused_handler(spawn):
def connection_refused_handler(spawn, context):
""" handles connection refused scenarios
"""
if spawn.device:
spawn.device.api.execute_clear_line()
spawn.device.connect()
return
context.setdefault('connection_refused_count', 0)
context['connection_refused_count'] += 1
if context.get('connection_refused_count') < spawn.settings.CONNECTION_REFUSED_MAX_COUNT:
if spawn.device:
spawn.device.api.execute_clear_line()
spawn.device.connect()
return
raise Exception('Connection refused to device %s' % (str(spawn)))


Expand Down
6 changes: 3 additions & 3 deletions src/unicon/plugins/iosxe/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ def __init__(self):
self.want_continue_confirm = r'.*Do you want to continue\?\s*\[confirm]\s*$'
self.want_continue_yes = r'.*Do you want to continue\?\s*\[y/n]\?\s*\[yes]:\s*$'
self.disable_prompt = \
r'^(.*?)(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?(\(recovery-mode\))?>\s?$'
r'^(.*?)(\(unlicensed\))?(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?(\(recovery-mode\))?>\s?$'
self.enable_prompt = \
r'^(.*?)(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?(\(recovery-mode\))?#[\s\x07]*$'
r'^(.*?)(\(unlicensed\))?(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?(\(recovery-mode\))?#[\s\x07]*$'
self.maintenance_mode_prompt = \
r'^(.*?)(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?\(maint-mode\)#[\s\x07]*$'
r'^(.*?)(\(unlicensed\))?(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|DDNS)\S*\)#\s?$'

Expand Down
1 change: 1 addition & 0 deletions src/unicon/plugins/iosxe/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def __init__(self):
r'routing table \S+ does not exist',
r'^%\s*SR feature is not configured yet, please enable Segment-routing first.',
r'^%\s*\S+ overlaps with \S+',
r'^\S+ / \S+ is an [Ii]nvalid network\.',
r'^%\S+ is linked to a VRF. Enable \S+ on that VRF first.',
r'% VRF \S+ not configured',
r'% Incomplete command.',
Expand Down
5 changes: 3 additions & 2 deletions src/unicon/plugins/iosxe/statements.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,11 @@ def boot_finished_deco(func):
'''

@wraps(func)
def wrapper(spawn, context, session):
def wrapper(spawn, session, context, **kwargs):
args = [a for a in [spawn, session, context] if a]
if context:
context.pop('boot_start_time', None)
return func(spawn)
return func(*args, **kwargs)
return wrapper


Expand Down
2 changes: 2 additions & 0 deletions src/unicon/plugins/iosxr/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ def __init__(self):
r'^%\s*Failed to commit.*'
]

self.HA_STANDBY_UNLOCK_COMMANDS = []

self.EXECUTE_MATCHED_RETRIES = 1
self.EXECUTE_MATCHED_RETRY_SLEEP = 0.1

Expand Down
4 changes: 2 additions & 2 deletions src/unicon/plugins/staros/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
class StarosPatterns(GenericPatterns):
def __init__(self):
super().__init__()
self.exec_prompt = r'^(.*?)(\[\S+\]%N[#>])\s*$'
self.config_prompt = r'^(.*?)(\[\S+\]%N\(\S+\)[#>])\s*$'
self.exec_prompt = r'^(.*?)(\[\S+\] ?%N[#>])\s*$'
self.config_prompt = r'^(.*?)(\[\S+\] ?%N\(\S+\)[#>])\s*$'
self.monitor_main_prompt = r'^(.*?\(Q\)uit,\s+<ESC> Prev Menu,\s+<SPACE> Pause,\s+<ENTER> Re-Display Options.*)$'
self.monitor_sub_prompt = r'^(.*?\(B\)egin Protocol Decoding\s+\(Q\)uit,\s+<ESC> Prev Menu,\s+<ENTER> Re-Display Options\s+Select:)\s*'
self.yes_no_prompt = r'^(.*?)Are you sure \? \[Yes | No\]:\s*'
Expand Down
13 changes: 13 additions & 0 deletions src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1654,6 +1654,12 @@ transition_to_general_enable2:
"":
new_state: general_enable

connection_refused_loop:
commands:
"":
response: |
telnet: connect to address 127.0.0.1: Connection refused


confirm_abort_copy:
preface: |
Expand Down Expand Up @@ -1681,3 +1687,10 @@ general_enable_no_operating_mode:
commands:
<<: *gen_enable_cmds
"show version | include operating mode" : "unexpected output"

unlicensed_prompt:
prompt: "(unlicensed)UUT#"
commands:
"show version | include operating mode": ""
"end":
new_state: general_enable
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ staros_connect:

staros_exec:
prompt: "[local]host_name# "
commands:
commands: &staros_exec_cmds
"terminal length 0": ""
"terminal width 512": ""
"no timestamps": ""
Expand Down Expand Up @@ -224,3 +224,16 @@ staros_call_finish:
prompt: ""
keys:
<<: *monitor_keys



staros_connect2:
preface: Escape character is '^]'.
prompt: ""
commands:
"":
new_state: staros_exec2

staros_exec2:
prompt: "[local] host_name# "
commands: *staros_exec_cmds
41 changes: 41 additions & 0 deletions src/unicon/plugins/tests/test_plugin_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1477,6 +1477,47 @@ def test_connection_refused_handler_without_peripheral(self):
d.disconnect()
md.stop()

def test_connection_refused_handler_repeat(self):
md = MockDeviceTcpWrapper(device_os='iosxe', hostname='R1',
port=0, state='connection_refused_loop')
md.start()
template_testbed = """
devices:
R1:
os: iosxe
credentials:
default:
username: cisco
password: cisco
connections:
defaults:
class: unicon.Unicon
a:
protocol: telnet
ip: 127.0.0.1
port: {}
peripherals:
terminal_server:
ts: [20]
ts:
os: ios
credentials:
default:
username: cisco
password: cisco
connections:
cli:
command: mock_device_cli --os ios --state exec --hostname ts
""".format(md.ports[0])
t = loader.load(template_testbed)
d = t.devices.R1
with self.assertRaisesRegex(unicon.core.errors.ConnectionError,
'failed to connect to R1'):
try:
d.connect()
finally:
d.disconnect()
md.stop()


if __name__ == "__main__":
Expand Down
12 changes: 12 additions & 0 deletions src/unicon/plugins/tests/test_plugin_iosxe.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,18 @@ def test_operating_mode_check(self):
finally:
c.disconnect()

def test_unlicensed_prompt(self):
c = Connection(hostname='UUT',
start=['mock_device_cli --os iosxe --state unlicensed_prompt --hostname UUT'],
os='iosxe',
credentials=dict(default=dict(username='cisco', password='cisco')),
mit=True)
try:
c.connect()
self.assertEqual(c.spawn.match.last_match.group(2), '(unlicensed)')
finally:
c.disconnect()


class TestIosXEPluginExecute(unittest.TestCase):

Expand Down
17 changes: 16 additions & 1 deletion src/unicon/plugins/tests/test_plugin_staros.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,21 @@ def test_monitor(self):
self.assertTrue('Call Finished - Waiting to trace next matching call' in r)


class TestStarosConnect(unittest.TestCase):

def test_connect(self):
c = Connection(hostname='host_name',
start=['mock_device_cli --os staros --state staros_connect2'],
os='staros',
username='cisco',
tacacs_password='cisco',
connection_timeout=15,
mit=True)
try:
c.connect()
finally:
c.disconnect()


if __name__ == "__main__":
unittest.main()

Loading
Loading