Skip to content

Commit cd4ea25

Browse files
author
Taarini Sarath Chander
committed
Releasing v25.2
1 parent 77b17b3 commit cd4ea25

28 files changed

+552
-35
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ PYPIREPO = pypitest
1111

1212
DEPENDENCIES = robotframework pyyaml dill coverage Sphinx \
1313
sphinxcontrib-napoleon sphinxcontrib-mockautodoc \
14-
sphinx-rtd-theme asyncssh PrettyTable "cryptography>=44.0"
14+
sphinx-rtd-theme asyncssh PrettyTable "cryptography>=43.0"
1515

1616

1717
.PHONY: clean package distribute develop undevelop help devnet\

docs/changelog/2025/february.rst

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
February 2025
2+
==========
3+
4+
February 25 - Unicon v25.2
5+
------------------------
6+
7+
8+
9+
.. csv-table:: Module Versions
10+
:header: "Modules", "Versions"
11+
12+
``unicon.plugins``, v25.2
13+
``unicon``, v25.2
14+
15+
16+
17+
18+
Changelogs
19+
^^^^^^^^^^
20+
--------------------------------------------------------------------------------
21+
Fix
22+
--------------------------------------------------------------------------------
23+
24+
* router.connection_provider
25+
* Modified disconnect
26+
* Added sendline('exit') on disconnect
27+
28+
29+
--------------------------------------------------------------------------------
30+
Fix
31+
--------------------------------------------------------------------------------
32+
33+
* generic
34+
* updated exception for Recover device using golden image if reload is failed.
35+
36+
37+
--------------------------------------------------------------------------------
38+
New
39+
--------------------------------------------------------------------------------
40+
41+
* iosxe/vpagent
42+
* Add connection provider for vpagent
43+
44+
* iosxe
45+
* Added to Configure Error Patterns
46+
* Added the regex to match error pattern "127.0 / 255.0 is an invalid network."
47+
48+

docs/changelog/2025/january.rst

+1-3
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,4 @@ Changelogs
5656
* Added below config error patterns
5757
* % VLAN [<vlan_id>] already in use
5858
* Added below config error patterns
59-
* % VNI <VNI_ID> is either already in use or exceeds the maximum allowable VNIs.
60-
61-
59+
* % VNI <VNI_ID> is either already in use or exceeds the maximum allowable VNIs.

docs/changelog/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Changelog
44
.. toctree::
55
:maxdepth: 2
66

7+
2025/february
78
2025/january
89
2024/november
910
2024/october
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
February 2025
2+
==========
3+
4+
February 25 - Unicon.Plugins v25.2
5+
------------------------
6+
7+
8+
9+
.. csv-table:: Module Versions
10+
:header: "Modules", "Versions"
11+
12+
``unicon.plugins``, v25.2
13+
``unicon``, v25.2
14+
15+
16+
17+
18+
Changelogs
19+
^^^^^^^^^^
20+
--------------------------------------------------------------------------------
21+
Fix
22+
--------------------------------------------------------------------------------
23+
24+
* generic
25+
* Updates to setup patterns
26+
* Update connection refused handler to clear line after max count
27+
* Update token discovery to use rv1 parsers
28+
* Update token discovery to handle standby locked devices
29+
30+
* staros
31+
* Update prompt pattern
32+
33+
* iosxe
34+
* Updated the enable, disable and maintenance states to support `(unlicensed)` prompt
35+
36+

docs/changelog_plugins/2025/january.rst

+1-3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,4 @@ Changelogs
3737
* update syslog message pattern
3838

3939
* unicon.plugins
40-
* Fix syntax warning
41-
42-
40+
* Fix syntax warning

docs/changelog_plugins/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Plugins Changelog
44
.. toctree::
55
:maxdepth: 2
66

7+
2025/february
78
2025/january
89
2024/november
910
2024/october

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def version_info(*paths):
5151
install_requires = ['unicon {range}'.format(range = version_range),
5252
'pyyaml',
5353
'PrettyTable',
54-
'cryptography>=44.0']
54+
'cryptography>=43.0']
5555

5656
# launch setup
5757
setup(

src/unicon/plugins/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = '25.1'
1+
__version__ = '25.2'
22

33
supported_chassis = [
44
'single_rp',

src/unicon/plugins/generic/patterns.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ def __init__(self):
5151
self.press_ctrlx = r"^(.*?)Press Ctrl\+x to Exit the session"
5252
self.connected = r'^(.*?)Connected.'
5353

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

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

src/unicon/plugins/generic/service_implementation.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1172,7 +1172,8 @@ def call_service(self,
11721172
except Exception as e:
11731173
if hasattr(con.device, 'clean') and hasattr(con.device.clean, 'device_recovery') and\
11741174
con.device.clean.device_recovery.get('golden_image'):
1175-
con.log.error(f'Reload failed booting device using golden image: {con.device.clean.device_recovery["golden_image"]}')
1175+
con.log.exception(f"Reload failed to install with file: {getattr(con.device.clean, 'images', [None])[0]}")
1176+
con.log.info(f'Booting the device using golden_image.')
11761177
con.device.api.device_recovery_boot(golden_image=con.device.clean.device_recovery['golden_image'])
11771178
con.log.info('Successfully booted the device using golden_image.')
11781179
raise

src/unicon/plugins/generic/settings.py

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def __init__(self):
5555
self.CONSOLE_TIMEOUT = 60
5656
self.BOOT_TIMEOUT = 600
5757
self.MAX_BOOT_ATTEMPTS = 3
58+
self.CONNECTION_REFUSED_MAX_COUNT = 3
5859

5960
# Temporary enable secret used during setup
6061
# this is used if no password is available

src/unicon/plugins/generic/statements.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,16 @@ def terminal_position_handler(spawn, session, context):
4343
spawn.send('\x1b[0;200R')
4444

4545

46-
def connection_refused_handler(spawn):
46+
def connection_refused_handler(spawn, context):
4747
""" handles connection refused scenarios
4848
"""
49-
if spawn.device:
50-
spawn.device.api.execute_clear_line()
51-
spawn.device.connect()
52-
return
49+
context.setdefault('connection_refused_count', 0)
50+
context['connection_refused_count'] += 1
51+
if context.get('connection_refused_count') < spawn.settings.CONNECTION_REFUSED_MAX_COUNT:
52+
if spawn.device:
53+
spawn.device.api.execute_clear_line()
54+
spawn.device.connect()
55+
return
5356
raise Exception('Connection refused to device %s' % (str(spawn)))
5457

5558

src/unicon/plugins/iosxe/patterns.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ def __init__(self):
2323
self.want_continue_confirm = r'.*Do you want to continue\?\s*\[confirm]\s*$'
2424
self.want_continue_yes = r'.*Do you want to continue\?\s*\[y/n]\?\s*\[yes]:\s*$'
2525
self.disable_prompt = \
26-
r'^(.*?)(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?(\(recovery-mode\))?>\s?$'
26+
r'^(.*?)(\(unlicensed\))?(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?(\(recovery-mode\))?>\s?$'
2727
self.enable_prompt = \
28-
r'^(.*?)(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?(\(recovery-mode\))?#[\s\x07]*$'
28+
r'^(.*?)(\(unlicensed\))?(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?(\(recovery-mode\))?#[\s\x07]*$'
2929
self.maintenance_mode_prompt = \
30-
r'^(.*?)(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?\(maint-mode\)#[\s\x07]*$'
30+
r'^(.*?)(\(unlicensed\))?(WLC|Router|RouterRP|Switch|ios|switch|%N)([0-9])?(\(standby\))?(-stby)?(-standby)?(\(boot\))?\(maint-mode\)#[\s\x07]*$'
3131
self.press_enter = ReloadPatterns().press_enter
3232
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?$'
3333

src/unicon/plugins/iosxe/settings.py

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def __init__(self):
2626
r'routing table \S+ does not exist',
2727
r'^%\s*SR feature is not configured yet, please enable Segment-routing first.',
2828
r'^%\s*\S+ overlaps with \S+',
29+
r'^\S+ / \S+ is an [Ii]nvalid network\.',
2930
r'^%\S+ is linked to a VRF. Enable \S+ on that VRF first.',
3031
r'% VRF \S+ not configured',
3132
r'% Incomplete command.',

src/unicon/plugins/iosxe/statements.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -230,10 +230,11 @@ def boot_finished_deco(func):
230230
'''
231231

232232
@wraps(func)
233-
def wrapper(spawn, context, session):
233+
def wrapper(spawn, session, context, **kwargs):
234+
args = [a for a in [spawn, session, context] if a]
234235
if context:
235236
context.pop('boot_start_time', None)
236-
return func(spawn)
237+
return func(*args, **kwargs)
237238
return wrapper
238239

239240

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from unicon.plugins.iosxe import IosXESingleRpConnection
2+
from unicon.plugins.iosxe.vpagent.settings import VpagentIosxeSettings
3+
from unicon.plugins.iosxe.vpagent.connection_provider import VpagentSingleRpConnectionProvider
4+
5+
6+
7+
class VpagentSingleRpConnection(IosXESingleRpConnection):
8+
platform = 'vpagent'
9+
chassis_type = 'single_rp'
10+
connection_provider_class = VpagentSingleRpConnectionProvider
11+
settings = VpagentIosxeSettings()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from time import sleep
2+
3+
from unicon.plugins.iosxe.connection_provider import IosxeSingleRpConnectionProvider
4+
5+
6+
class VpagentSingleRpConnectionProvider(IosxeSingleRpConnectionProvider):
7+
""" Implements Vpagent singleRP Connection Provider,
8+
This class overrides the base class with the
9+
additional dialogs and steps required for
10+
connecting to any device via generic implementation
11+
"""
12+
13+
def __init__(self, *args, **kwargs):
14+
15+
""" Initializes the generic connection provider
16+
"""
17+
super().__init__(*args, **kwargs)
18+
19+
20+
def establish_connection(self):
21+
""" Reads the device state and brings it to the right state
22+
Note: Passive hostname learning is enabled by default and will
23+
give a warning if the device hostname does not match the learned
24+
hostname. The learned hostname is only used if user specifies
25+
`learn_hostname=True`. A timeout may occur if the prompt pattern
26+
uses the hostname, the timeout error includes the hostname and
27+
a hint to check the hostname if a mismatch was detected.
28+
"""
29+
con = self.connection
30+
31+
# Enable hostname learning by default
32+
con.state_machine.learn_hostname = True
33+
con.state_machine.learn_pattern = con.settings.DEFAULT_LEARNED_HOSTNAME
34+
35+
context = self.connection.context
36+
if (login_creds := context.get('login_creds')):
37+
context.update(cred_list=login_creds)
38+
39+
# Before accessing the vm since device is not ready and connection may be
40+
# closed by vcenter manger we need to wait before accessing the device.
41+
timeout = con.settings.WAITE_TIMEOUT
42+
con.log.info(f'sleeping for {timeout} seconds before accessing the device!')
43+
sleep(timeout)
44+
45+
dialog = self.get_connection_dialog()
46+
# Try to bring device to any state connection may be closed during bring the device
47+
# to a valid and cause an IO error
48+
output = self._get_device_to_any(con, context, dialog)
49+
# if device is still in generic state that means we could not bring device to any state and
50+
# spawn is closed so we need to create a new spawn and try again to bring device to any state
51+
if con.state_machine.current_state == 'generic':
52+
con.setup_connection()
53+
output = self._get_device_to_any(con, context, dialog)
54+
55+
if con.state_machine.current_state == "config":
56+
con.state_machine.go_to('enable',
57+
self.connection.spawn,
58+
context=context,
59+
prompt_recovery=self.prompt_recovery,
60+
timeout=self.connection.connection_timeout)
61+
62+
if con.state_machine.current_state not in ['rommon', 'standby_locked', 'shell']:
63+
cur_state = con.state_machine.get_state(con.state_machine.current_state)
64+
# if the learn hostname is set to True the pattern for state machine updated with
65+
# '(?P<hostname00...)' regex patterns so we check for the pattern before learning the hostname
66+
if 'P<hostname' in cur_state.pattern:
67+
self.learn_hostname()
68+
# If device is found in one of the above states, init_handle()
69+
# will change the device state to enable, after which
70+
# hostname learning and token discovery is done.
71+
self.learn_tokens()
72+
73+
con.state_machine.learn_hostname = False
74+
context.pop('cred_list', None)
75+
76+
return output
77+
78+
def _get_device_to_any(self, connection, context, dialog):
79+
try:
80+
connection.state_machine.go_to('any',
81+
self.connection.spawn,
82+
context=context,
83+
prompt_recovery=self.prompt_recovery,
84+
timeout=self.connection.connection_timeout,
85+
dialog=dialog)
86+
except Exception:
87+
connection.setup_connection()
88+
self._get_device_to_any(connection, context, dialog)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
""" Vpagent IOS-XE Settings. """
2+
3+
from unicon.plugins.iosxe.settings import IosXESettings
4+
5+
class VpagentIosxeSettings(IosXESettings):
6+
7+
def __init__(self):
8+
super().__init__()
9+
self.WAITE_TIMEOUT = 60

src/unicon/plugins/iosxr/settings.py

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ def __init__(self):
4949
r'^%\s*Failed to commit.*'
5050
]
5151

52+
self.HA_STANDBY_UNLOCK_COMMANDS = []
53+
5254
self.EXECUTE_MATCHED_RETRIES = 1
5355
self.EXECUTE_MATCHED_RETRY_SLEEP = 0.1
5456

src/unicon/plugins/staros/patterns.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
class StarosPatterns(GenericPatterns):
66
def __init__(self):
77
super().__init__()
8-
self.exec_prompt = r'^(.*?)(\[\S+\]%N[#>])\s*$'
9-
self.config_prompt = r'^(.*?)(\[\S+\]%N\(\S+\)[#>])\s*$'
8+
self.exec_prompt = r'^(.*?)(\[\S+\] ?%N[#>])\s*$'
9+
self.config_prompt = r'^(.*?)(\[\S+\] ?%N\(\S+\)[#>])\s*$'
1010
self.monitor_main_prompt = r'^(.*?\(Q\)uit,\s+<ESC> Prev Menu,\s+<SPACE> Pause,\s+<ENTER> Re-Display Options.*)$'
1111
self.monitor_sub_prompt = r'^(.*?\(B\)egin Protocol Decoding\s+\(Q\)uit,\s+<ESC> Prev Menu,\s+<ENTER> Re-Display Options\s+Select:)\s*'
1212
self.yes_no_prompt = r'^(.*?)Are you sure \? \[Yes | No\]:\s*'

src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml

+13
Original file line numberDiff line numberDiff line change
@@ -1654,6 +1654,12 @@ transition_to_general_enable2:
16541654
"":
16551655
new_state: general_enable
16561656

1657+
connection_refused_loop:
1658+
commands:
1659+
"":
1660+
response: |
1661+
telnet: connect to address 127.0.0.1: Connection refused
1662+
16571663
16581664
confirm_abort_copy:
16591665
preface: |
@@ -1681,3 +1687,10 @@ general_enable_no_operating_mode:
16811687
commands:
16821688
<<: *gen_enable_cmds
16831689
"show version | include operating mode" : "unexpected output"
1690+
1691+
unlicensed_prompt:
1692+
prompt: "(unlicensed)UUT#"
1693+
commands:
1694+
"show version | include operating mode": ""
1695+
"end":
1696+
new_state: general_enable

0 commit comments

Comments
 (0)