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 .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:

strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
group: [1, 2, 3, 4, 5]
steps:
- uses: actions/checkout@v2
Expand Down
42 changes: 42 additions & 0 deletions docs/changelog/2025/july.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
July 2025
==========

July 29 - Unicon v25.7
------------------------



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

``unicon.plugins``, v25.7
``unicon``, v25.7




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

* bases/router
* connection_provider
* Updated logout service logic for single rp and multi rp connections

* router/connection
* Initialized the UNICON_BACKEND_DECODE_ERROR_LIMIT to None for iosxr HA device connections


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

* mock device
* Add mock device for svl stack

* iosxe
* Added cert-trustpool config pattern


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/july
2025/june
2025/may
2025/april
Expand Down
39 changes: 39 additions & 0 deletions docs/changelog_plugins/2025/july.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
July 2025
==========

July 29 - Unicon.Plugins v25.7
------------------------



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

``unicon.plugins``, v25.7
``unicon``, v25.7




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

* iosxr
* switchover
* Fixed timeout handling by using explicit timeout parameter instead of self.timeout.
* Update monitor service prompt pattern
* Increase monitor stop timeout
* Update execute() service to exit unsupported modes (e.g. monitor mode)


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

* iosxe
* Added support for 9500 and 9500x SVL switchover


This file was deleted.

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/july
2025/june
2025/may
2025/april
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
'sphinx.ext.autodoc',
'sphinx.ext.napoleon',
'sphinx.ext.intersphinx',
'sphinxcontrib.mockautodoc',
# 'sphinxcontrib.mockautodoc',
]

if os.environ.get('DEVNET', None) == 'true':
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.6"
__version__ = "25.7"

supported_chassis = [
'single_rp',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class IosXEC9500xStackwiseVirtualServiceList(StackIosXEServiceList):
def __init__(self):
super().__init__()
self.reload = svc.SVLStackReload
self.switchover = svc.SVLStackSwitchover


class IosXEC9500xStackwiseVirtualRPConnection(IosXEStackRPConnection):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from unicon.plugins.iosxe.stack.utils import StackUtils
from unicon.plugins.generic.statements import custom_auth_statements
from unicon.plugins.iosxe.stack.service_statements import (switch_prompt,
stack_reload_stmt_list)
stack_reload_stmt_list,
stack_switchover_stmt_list)

utils = StackUtils()

Expand Down Expand Up @@ -247,3 +248,110 @@ def boot(con):
if return_output:
Result = namedtuple('Result', ['result', 'output'])
self.result = Result(self.result, reload_cmd_output.match_output.replace(reload_cmd, '', 1))


class SVLStackSwitchover(BaseService):
""" Get Rp state

Service to get the redundancy state of the device rp.

Arguments:
target: Service target, by default active

Returns:
Expected return values are ACTIVE, STANDBY, MEMBER
raise SubCommandFailure on failure.

Example:
.. code-block:: python

rtr.get_rp_state()
rtr.get_rp_state(target='standby')
"""

def __init__(self, connection, context, **kwargs):
super().__init__(connection, context, **kwargs)
self.start_state = 'enable'
self.end_state = 'enable'
self.timeout = connection.settings.STACK_SWITCHOVER_TIMEOUT
self.command = "redundancy force-switchover"
self.dialog = Dialog(stack_switchover_stmt_list)
self.__dict__.update(kwargs)

def call_service(self, command=None,
reply=Dialog([]),
timeout=None,
*args, **kwargs):

switchover_cmd = command or self.command
timeout = timeout or self.timeout
conn = self.connection.active

expected_active_sw = self.connection.standby.member_id
dialog = self.dialog

if reply:
dialog = reply + self.dialog

# added connection dialog in case switchover ask for username/password
connect_dialog = self.connection.connection_provider.get_connection_dialog()
dialog += connect_dialog

conn.log.info('Processing on active rp %s-%s' % (conn.hostname, conn.alias))
conn.sendline(switchover_cmd)
try:
# A loop has been implemented to handle the
# "Press RETURN to get started" prompt twice. Based on extensive
# testing during SVL reloads on 9500x devices, it was observed
# that the device is not fully ready after the first prompt.
# As a result, the logic accounts for this behavior by waiting for
# the second occurrence of the message, which is assumed to be the
# default behavior for these devices.
for _ in range(2):
match_object = dialog.process(conn.spawn, timeout=timeout,
prompt_recovery=self.prompt_recovery,
context=conn.context)
except Exception as e:
raise SubCommandFailure('Error during switchover ', e) from e

# try boot up original active rp with current active system
# image, if it moved to rommon state.
if 'state' in conn.context and conn.context.state == 'rommon':
try:
conn.state_machine.detect_state(conn.spawn, context=conn.context)
conn.state_machine.go_to('enable', conn.spawn, timeout=timeout,
prompt_recovery=self.prompt_recovery,
context=conn.context, dialog=Dialog([switch_prompt]))
except Exception as e:
self.connection.log.warning('Fail to bring up original active rp from rommon state.', e)
finally:
conn.context.pop('state')

# To ensure the stack is ready to accept the login
self.connection.log.info('Sleeping for %s secs.' % \
self.connection.settings.POST_SWITCHOVER_SLEEP)
sleep(self.connection.settings.POST_SWITCHOVER_SLEEP)

# check all members are ready
conn.state_machine.detect_state(conn.spawn, context=conn.context)

interval = self.connection.settings.SWITCHOVER_POSTCHECK_INTERVAL
if utils.is_all_member_ready(conn, timeout=timeout, interval=interval):
self.connection.log.info('All members are ready.')
else:
self.connection.log.info('Timeout in %s secs. '
'Not all members are in Ready state.' % timeout)
self.result = False
return

self.connection.log.info('Disconnecting and reconnecting')
self.connection.disconnect()
self.connection.connect()

self.connection.log.info('Verifying active and standby switch State.')
if self.connection.active.member_id == expected_active_sw:
self.connection.log.info('Switchover successful')
self.result = True
else:
self.connection.log.info('Switchover failed')
self.result = False
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
from unicon.plugins.iosxe.stack import IosXEStackRPConnection
from .connection_provider import StackwiseVirtualConnectionProvider

from unicon.plugins.iosxe.stack.service_implementation import StackReload
from unicon.plugins.iosxe.stack.service_implementation import StackReload, StackSwitchover


class IosXECat9kStackwiseVirtualServiceList(StackIosXEServiceList):

def __init__(self):
super().__init__()
self.reload = StackReload
self.switchover = StackSwitchover


class IosXECat9kStackwiseVirtualRPConnection(IosXEStackRPConnection):
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'^(.*?)(\(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|ca-trustpool)\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|ca-trustpool|cert-trustpool)\S*\)#\s?$'


self.config_pki_prompt = r'^(.*)\(config-pki-hexmode\)#\s?$'
Expand Down
2 changes: 1 addition & 1 deletion src/unicon/plugins/iosxr/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,4 @@ def __init__(self):
# 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"\s*(?P<command>[\w\s]+)='(?P<key>\w+)'"
self.monitor_command_pattern = r"\s*(?P<command>[\w ]+)='(?P<key>\w+)'"
18 changes: 16 additions & 2 deletions src/unicon/plugins/iosxr/service_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ def __init__(self, connection, context, **kwargs):
# Connection object will have all the received details
super().__init__(connection, context, **kwargs)
self.dialog += Dialog(execution_statement_list)
self.UNSUPPORTED_START_STATES = ['monitor']

def pre_service(self, *args, **kwargs):
# If the connection is in a unsupported state, go to enable mode
if self.connection.state_machine.current_state in \
self.UNSUPPORTED_START_STATES:
sm = self.get_sm()
sm.go_to('enable',
self.connection.spawn,
timeout=self.connection.spawn.settings.EXEC_TIMEOUT)
super().pre_service(*args, **kwargs)


class Configure(svc.Configure):
Expand Down Expand Up @@ -194,7 +205,7 @@ def call_service(self, command='redundancy switchover',
con.active.spawn.sendline(command)
try:
self.result = dialog.process(con.active.spawn,
timeout=self.timeout,
timeout=timeout,
prompt_recovery=self.prompt_recovery,
context=con.active.context)
except SubCommandFailure as err:
Expand Down Expand Up @@ -571,7 +582,10 @@ def stop(self):
conn.log.info('Monitor not running')
return

conn.state_machine.go_to('enable', conn.spawn)
conn.state_machine.go_to(
'enable',
conn.spawn,
timeout=conn.spawn.settings.EXEC_TIMEOUT)

# Grab output after stopping monitor
output = self.get_buffer(truncate=True)
Expand Down
Loading
Loading